From f33c4d42bffe8d149cd4b35369a2b61b7800039e Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Mon, 27 Jan 2025 08:11:28 +0100 Subject: [PATCH] refactor: refs #6897 clean up unused code, enhance input components, and add new localization entries --- src/boot/quasar.js | 4 +- src/components/VnColor.vue | 31 -- src/components/VnTable/VnColumn.vue | 14 +- src/components/VnTable/VnFilter.vue | 12 +- src/components/VnTable/VnTable.vue | 370 ++++++++++++------ src/components/common/VnColor.vue | 32 ++ src/components/common/VnComponent.vue | 2 + src/components/common/VnInput.vue | 4 - src/components/common/VnSelect.vue | 3 +- src/components/common/VnSelectDialog.vue | 2 - src/css/app.scss | 1 - src/i18n/locale/en.yml | 27 +- src/i18n/locale/es.yml | 25 +- src/pages/Customer/CustomerList.vue | 11 - .../components/CustomerAddressEdit.vue | 8 +- src/pages/Entry/Card/<!DOCTYPE html>.html | 68 ---- src/pages/Entry/Card/EntryBasicData.vue | 50 +-- src/pages/Entry/Card/EntryBuys.vue | 324 +++++++++++++-- src/pages/Entry/Card/EntryDescriptor.vue | 72 +++- src/pages/Entry/Card/EntryFilter.js | 11 +- src/pages/Entry/Card/EntrySummary.vue | 238 ++++------- src/pages/Entry/EntryFilter.vue | 245 +++++++----- src/pages/Entry/EntryList.vue | 340 +++++++++------- src/pages/Item/Card/ItemDescriptor.vue | 16 +- src/pages/Item/Card/ItemDescriptorProxy.vue | 3 +- src/pages/Item/locale/en.yml | 1 + src/pages/Item/locale/es.yml | 1 + 27 files changed, 1165 insertions(+), 750 deletions(-) delete mode 100644 src/components/VnColor.vue create mode 100644 src/components/common/VnColor.vue delete mode 100644 src/pages/Entry/Card/<!DOCTYPE html>.html diff --git a/src/boot/quasar.js b/src/boot/quasar.js index e1e879315..a8c397b83 100644 --- a/src/boot/quasar.js +++ b/src/boot/quasar.js @@ -51,7 +51,5 @@ export default boot(({ app }) => { await useCau(response, message); }; - app.directive('shortcut', keyShortcut); - app.mixin(qFormMixin); - app.mixin(mainShortcutMixin); + app.provide('app', app); }); diff --git a/src/components/VnColor.vue b/src/components/VnColor.vue deleted file mode 100644 index 73c898ce3..000000000 --- a/src/components/VnColor.vue +++ /dev/null @@ -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', - }" - > - - </div> - </div> -</template> -<style scoped> -.color-div { - display: flex; - flex-direction: column; -} -</style> diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index baa576bba..0040385c5 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -1,6 +1,6 @@ <script setup> import { markRaw, computed } from 'vue'; -import { QIcon, QCheckbox } from 'quasar'; +import { QIcon, QCheckbox, QToggle } from 'quasar'; import { dashIfEmpty } from 'src/filters'; /* basic input */ @@ -48,6 +48,10 @@ const $props = defineProps({ type: Boolean, default: null, }, + eventHandlers: { + type: Object, + default: null, + }, }); const defaultSelect = { @@ -141,6 +145,9 @@ const defaultComponents = { userLink: { component: markRaw(VnUserLink), }, + toggle: { + component: markRaw(QToggle), + }, }; const value = computed(() => { @@ -187,6 +194,7 @@ const components = computed(() => { ...(component.attrs || {}), autofocus: $props.autofocus, }, + event: { ...component?.event, ...$props?.eventHandlers }, }; return acc; }, {}); @@ -200,7 +208,6 @@ const components = computed(() => { :components="components" :value="{ row, model }" v-model="model" - @blur="emit('blur')" /> <VnComponent v-if="col.component" @@ -208,7 +215,6 @@ const components = computed(() => { :components="components" :value="{ row, model }" v-model="model" - @blur="emit('blur')" /> <span :title="value" v-else>{{ value }}</span> <VnComponent @@ -217,7 +223,7 @@ const components = computed(() => { :components="components" :value="{ row, model }" v-model="model" - @blur="emit('blur')" /> + <slot name="append" /> </div> </template> diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 77fa2e246..1618f4f5a 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -58,7 +58,7 @@ const selectComponent = { component: markRaw(VnSelect), event: updateEvent, attrs: { - class: 'q-px-sm q-pb-xs q-pt-none fit', + class: 'q-pt-none fit test', dense: true, filled: !$props.showTitle, }, @@ -120,6 +120,7 @@ const components = { }; async function addFilter(value, name) { + console.log('test'); value ??= undefined; if (value && typeof value === 'object') value = model.value; value = value === '' ? undefined : value; @@ -168,3 +169,12 @@ const onTabPressed = async () => { /> </div> </template> +<style lang="scss"> +/* label.test { + padding-bottom: 0px !important; + background-color: red; + } */ +label.test > .q-field__inner > .q-field__control { + padding: inherit; +} +</style> diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index b8923129f..7f5808627 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,23 +1,36 @@ <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 { useRoute, useRouter } from 'vue-router'; import { useQuasar } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; import { useFilterParams } from 'src/composables/useFilterParams'; +import { dashIfEmpty } from 'src/filters'; import CrudModel from 'src/components/CrudModel.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 VnTableChip from 'components/VnTable/VnChip.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; import VnTableFilter from './VnTableFilter.vue'; -import { dashIfEmpty } from 'src/filters'; +const arrayData = useArrayData(useAttrs()['data-key']); const $props = defineProps({ columns: { type: Array, @@ -115,6 +128,10 @@ const $props = defineProps({ type: Boolean, default: false, }, + withFilters: { + type: Boolean, + default: true, + }, }); const { t } = useI18n(); const stateStore = useStateStore(); @@ -136,7 +153,12 @@ const createForm = ref(); const tableRef = ref(); const params = ref(useFilterParams($attrs['data-key']).params); 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 = [ { icon: 'view_column', @@ -156,7 +178,6 @@ onBeforeMount(() => { const urlParams = route.query[$props.searchUrl]; hasParams.value = urlParams && Object.keys(urlParams).length !== 0; }); - onMounted(() => { mode.value = quasar.platform.is.mobile && !$props.disableOption?.card @@ -185,9 +206,6 @@ watch( { immediate: true } ); -const isTableMode = computed(() => mode.value == TABLE_MODE); -const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); - function splitColumns(columns) { splittedColumns.value = { columns: [], @@ -306,99 +324,210 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { } } -const editingRow = ref(null); -const editingField = ref(null); +function isEditableColumn(column) { + 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'); if (!clickedElement) return; const rowIndex = clickedElement.getAttribute('data-row-index'); + console.log('HandleRowIndex: ', rowIndex); const colField = clickedElement.getAttribute('data-col-field'); + console.log('HandleColField: ', colField); if (rowIndex !== null && colField) { - startEditing(Number(rowIndex), colField); - } -}; -const vnEditableCell = ref(null); -const startEditing = async (rowId, field) => { - const col = $props.columns.find((col) => col.name === field); - 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(); + console.log('handleClick STARTEDEDITING'); + const column = $props.columns.find((col) => col.name === colField); + console.log('isEditableColumn(column): ', isEditableColumn(column)); + if (!isEditableColumn(column)) return; + await startEditing(Number(rowIndex), colField, clickedElement); + if (column.component !== 'checkbox') console.log(); } }; -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; editingRow.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 totalColumns = columns.length; 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) => { - console.log('handleShiftTab: '); - const columns = $props.columns; - const currentColumnIndex = columns.findIndex((col) => col.name === colName); + if (isEditableColumn(columns[newColumnIndex])) break; + } while (iterations < totalColumns); - if (currentColumnIndex === -1) return; - - 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); + if (iterations >= totalColumns) { + console.warn('No editable columns found.'); return; } - await startEditing(prevRowIndex, columns[prevColumnIndex]?.name); - console.log('finishHandleShiftTab'); -}; + if (direction === 1 && newColumnIndex <= currentColumnIndex) { + 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) { switch (typeof value) { case 'boolean': @@ -408,25 +537,35 @@ function getCheckboxIcon(value) { ? 'indeterminate_check_box' : 'unknown_med'; case 'number': - return value > 0 ? 'check' : 'close'; + return value === 0 ? 'close' : 'check'; case 'object': return value === null ? 'help_outline' : 'unknown_med'; case 'undefined': return 'help_outline'; default: - return 'unknown_med'; + return 'indeterminate_check_box'; } } -function shouldDisplayReadonly(col, rowIndex) { - return ( - !col?.component || - editingRow.value !== rowIndex || - editingField.value !== col?.name - ); -} +/* function getCheckboxIcon(value) { + switch (typeof value) { + case 'boolean': + return value ? 'check_box' : 'check_box_outline_blank'; + 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> - <template> <QDrawer v-if="$props.rightSearch" @@ -477,7 +616,7 @@ function shouldDisplayReadonly(col, rowIndex) { @row-click="(_, row) => rowClickFunction && rowClickFunction(row)" @update:selected="emit('update:selected', $event)" @selection="(details) => handleSelection(details, rows)" - @click="handleClick" + v-on="isEditable ? { click: handleClick } : {}" > <template #top-left v-if="!$props.withoutHeader"> <slot name="top-left"> </slot> @@ -496,13 +635,6 @@ function shouldDisplayReadonly(col, rowIndex) { dense :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 #header-cell="{ col }"> <QTh @@ -512,7 +644,9 @@ function shouldDisplayReadonly(col, rowIndex) { > <div class="no-padding" - :style="$props.columnSearch ? 'height: 75px' : ''" + :style=" + withFilters && $props.columnSearch ? 'height: 75px' : '' + " > <div class="text-center" style="height: 30px"> <QTooltip v-if="col.toolTip">{{ col.toolTip }}</QTooltip> @@ -525,13 +659,17 @@ function shouldDisplayReadonly(col, rowIndex) { /> </div> <VnFilter - v-if="$props.columnSearch && col.columnSearch !== false" + v-if=" + $props.columnSearch && + col.columnSearch !== false && + withFilters + " :column="col" :show-title="true" :data-key="$attrs['data-key']" v-model="params[columnName(col)]" :search-url="searchUrl" - class="full-width" + class="full-width test" /> </div> </QTh> @@ -550,7 +688,6 @@ function shouldDisplayReadonly(col, rowIndex) { </template> <template #body-cell="{ col, row, rowIndex }"> <QTd - auto-width class="no-margin q-px-xs" v-if="col.visible ?? true" :style="{ @@ -566,7 +703,6 @@ function shouldDisplayReadonly(col, rowIndex) { :data-col-field="col?.name" > <div - v-if="shouldDisplayReadonly(col, rowIndex)" class="no-padding no-margin" style=" overflow: hidden; @@ -584,18 +720,12 @@ function shouldDisplayReadonly(col, rowIndex) { v-if="col?.component === 'checkbox'" :name="getCheckboxIcon(row[col?.name])" style="color: var(--vn-text-color)" - size="var(--font-size)" - :class=" - isEditable && - (col?.component ? 'editable-text' : '') - " + :class="hasEditableFormat(col)" + size="17px" /> <span v-else - :class=" - isEditable && - (col?.component ? 'editable-text' : '') - " + :class="hasEditableFormat(col)" :style="col?.style ? col.style(row) : null" style="bottom: 0" > @@ -607,27 +737,6 @@ function shouldDisplayReadonly(col, rowIndex) { </span> </slot> </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> </template> <template #body-cell-tableActions="{ col, row }"> @@ -751,7 +860,7 @@ function shouldDisplayReadonly(col, rowIndex) { :row="row" :row-index="index" > - <VnTableColumn + <VnColumn :column="col" :row="row" :is-editable="false" @@ -792,7 +901,8 @@ function shouldDisplayReadonly(col, rowIndex) { </component> </template> <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 v-for="col of cols.filter((cols) => cols.visible ?? true)" :key="col?.id" @@ -838,7 +948,7 @@ function shouldDisplayReadonly(col, rowIndex) { :column-name="column.name" :label="column.label" > - <VnTableColumn + <VnColumn :column="column" :row="{}" default="input" diff --git a/src/components/common/VnColor.vue b/src/components/common/VnColor.vue new file mode 100644 index 000000000..00e662bb8 --- /dev/null +++ b/src/components/common/VnColor.vue @@ -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`, + }" + > + + </div> + </div> +</template> +<style scoped> +.color-div { + display: flex; + flex-direction: column; +} +</style> diff --git a/src/components/common/VnComponent.vue b/src/components/common/VnComponent.vue index d9d1ea26b..c1700fd45 100644 --- a/src/components/common/VnComponent.vue +++ b/src/components/common/VnComponent.vue @@ -45,6 +45,7 @@ function toValueAttrs(attrs) { } </script> <template> + <slot name="test" /> <span v-for="toComponent of componentArray" :key="toComponent.name" @@ -57,6 +58,7 @@ function toValueAttrs(attrs) { v-on="mix(toComponent).event ?? {}" v-model="model" @blur="emit('blur')" + @mouse-down="() => console.log('mouse-down')" /> </span> </template> diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 1b896611a..7981ac683 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -70,10 +70,6 @@ const focus = () => { vnInputRef.value.focus(); }; -defineExpose({ - focus, -}); - const mixinRules = [ requiredFieldRule, ...($attrs.rules ?? []), diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 2dbb43f1e..8aa725b4a 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -6,7 +6,7 @@ import { useRequired } from 'src/composables/useRequired'; import dataByOrder from 'src/utils/dataByOrder'; 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 { t } = useI18n(); @@ -327,7 +327,6 @@ function handleKeyDown(event) { :option-value="optionValue" v-bind="{ ...$attrs, ...styleAttrs }" @filter="filterHandler" - @blur="() => emit('blur')" :emit-value="nullishToTrue($attrs['emit-value'])" :map-options="nullishToTrue($attrs['map-options'])" :use-input="nullishToTrue($attrs['use-input'])" diff --git a/src/components/common/VnSelectDialog.vue b/src/components/common/VnSelectDialog.vue index 12322c3fa..5944a1ea7 100644 --- a/src/components/common/VnSelectDialog.vue +++ b/src/components/common/VnSelectDialog.vue @@ -34,7 +34,6 @@ const isAllowedToCreate = computed(() => { return role.hasAny($props.rolesAllowedToCreate); }); </script> - <template> <VnSelect v-model="value" @@ -63,7 +62,6 @@ const isAllowedToCreate = computed(() => { </template> </VnSelect> </template> - <style lang="scss" scoped> .default-icon { cursor: pointer; diff --git a/src/css/app.scss b/src/css/app.scss index 79088b2b2..7461d7c73 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -76,7 +76,6 @@ a { text-decoration: underline; } -// Removes chrome autofill background input:-webkit-autofill, select:-webkit-autofill { color: var(--vn-text-color); diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 4a78811e6..82ac717de 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -392,16 +392,26 @@ entry: list: newEntry: New entry tableVisibleColumns: - created: Creation - supplierFk: Supplier - isBooked: Booked - isConfirmed: Confirmed + isExcludedFromAvailable: Exclude from inventory 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 - travelFk: Travel - isExcludedFromAvailable: Inventory + evaNotes: Notes + warehouseOutFk: Origin + warehouseInFk: Destiny + entryTypeDescription: Entry type invoiceAmount: Import + travelFk: Travel summary: + invoiceAmount: Amount commission: Commission currency: Currency invoiceNumber: Invoice number @@ -454,7 +464,10 @@ entry: ektFk: Ekt packingOut: Package out landing: Landing - isExcludedFromAvailable: Es inventory + isExcludedFromAvailable: Exclude from inventory + isRaid: Raid + invoiceNumber: Invoice + reference: Ref/Alb/Guide ticket: card: customerId: Customer ID diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 2bfe7ec4b..c94bb5a46 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -392,16 +392,26 @@ entry: list: newEntry: Nueva entrada tableVisibleColumns: - created: Creación - supplierFk: Proveedor - isBooked: Asentado - isConfirmed: Confirmado + 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 - isExcludedFromAvailable: Inventario + evaNotes: Notas + warehouseOutFk: Origen + warehouseInFk: Destino + entryTypeDescription: Tipo entrada invoiceAmount: Importe summary: + invoiceAmount: Importe commission: Comisión currency: Moneda invoiceNumber: Núm. factura @@ -455,7 +465,10 @@ entry: ektFk: Ekt packingOut: Embalaje envíos landing: Llegada - isExcludedFromAvailable: Es inventario + isExcludedFromAvailable: Excluir del inventario + isRaid: Redada + invoiceNumber: Nº Factura + reference: Ref/Alb/Guía ticket: card: customerId: ID cliente diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index fdfd7ff9c..85e81bac6 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -100,17 +100,6 @@ const columns = computed(() => [ columnFilter: { component: 'number', }, - columnField: { - component: null, - after: { - component: markRaw(VnLinkPhone), - attrs: ({ model }) => { - return { - 'phone-number': model, - }; - }, - }, - }, }, { align: 'left', diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index 150ef3b84..f73e42449 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -247,8 +247,14 @@ function handleLocation(data, location) { :label="t('Longitude')" clearable 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> <h4 class="q-mb-xs">{{ t('Notes') }}</h4> <VnRow diff --git a/src/pages/Entry/Card/<!DOCTYPE html>.html b/src/pages/Entry/Card/<!DOCTYPE html>.html deleted file mode 100644 index 3652ce443..000000000 --- a/src/pages/Entry/Card/<!DOCTYPE html>.html +++ /dev/null @@ -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> diff --git a/src/pages/Entry/Card/EntryBasicData.vue b/src/pages/Entry/Card/EntryBasicData.vue index 147287837..87bb3bfec 100644 --- a/src/pages/Entry/Card/EntryBasicData.vue +++ b/src/pages/Entry/Card/EntryBasicData.vue @@ -9,6 +9,7 @@ import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import FilterTravelForm from 'src/components/FilterTravelForm.vue'; @@ -51,28 +52,6 @@ const onFilterTravelSelected = (formData, id) => { > <template #form="{ data }"> <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 :label="t('entry.basicData.travel')" v-model="data.travelFk" @@ -105,9 +84,36 @@ const onFilterTravelSelected = (formData, id) => { </QItem> </template> </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> <VnInput v-model="data.reference" :label="t('globals.reference')" /> + <VnInputNumber + v-model="data.invoiceAmount" + :label="t('entry.summary.invoiceAmount')" + :positive="false" + /> </VnRow> <VnRow> <VnInput diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue index f470cf08a..9ea150cd9 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -2,21 +2,36 @@ import { useStateStore } from 'stores/useStateStore'; import { useRoute } from 'vue-router'; 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 ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; -import FetchedTags from 'components/ui/FetchedTags.vue'; -import VnColor from 'src/components/VnColor.vue'; +import FetchedTags from 'src/components/ui/FetchedTags.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 stateStore = useStateStore(); const route = useRoute(); const selectedRows = ref([]); +const entityId = ref($props.id ?? route.params.id); +console.log('entityId: ', entityId.value); + +const footer = ref({}); const columns = [ { - name: 'buyFk', + name: 'id', isId: true, visible: false, isEditable: false, @@ -26,6 +41,7 @@ const columns = [ label: 'Nv', name: 'isIgnored', component: 'checkbox', + toggleIndeterminate: false, width: '35px', }, { @@ -33,6 +49,7 @@ const columns = [ label: 'Id', name: 'itemFk', component: 'input', + isEditable: false, create: true, width: '45px', }, @@ -52,7 +69,8 @@ const columns = [ }, { align: 'center', - label: t('Size'), + label: t('Siz.'), + toolTip: t('Size'), name: 'size', width: '35px', isEditable: false, @@ -63,8 +81,18 @@ const columns = [ { align: 'center', label: t('Sti.'), + toolTip: t('Printed Stickers/Stickers'), name: 'stickers', component: 'number', + attrs: { + positive: false, + }, + cellEvent: { + 'update:modelValue': (value, oldValue, row) => { + row['quantity'] = value * row['packing']; + row['amount'] = row['quantity'] * row['buyingValue']; + }, + }, width: '35px', }, { @@ -92,11 +120,49 @@ const columns = [ label: 'Pack', name: 'packing', 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', style: (row) => { if (row.groupingMode === 'grouping') 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', @@ -113,17 +179,31 @@ const columns = [ label: t('Quantity'), name: 'quantity', component: 'number', - width: '50px', - style: (row) => { - if (row?.quantity !== row?.stickers * row?.packing) - return { color: 'var(--q-negative)' }; + attrs: { + positive: false, }, + cellEvent: { + 'update:modelValue': (value, oldValue, row) => { + row['amount'] = value * row['buyingValue']; + }, + }, + width: '50px', + style: getQuantityStyle, }, { align: 'center', - label: 'Cost.', + label: t('Cost'), + toolTip: t('Buying value'), name: 'buyingValue', component: 'number', + attrs: { + positive: false, + }, + cellEvent: { + 'update:modelValue': (value, oldValue, row) => { + row['amount'] = row['quantity'] * value; + }, + }, width: '50px', }, { @@ -131,13 +211,17 @@ const columns = [ label: t('Amount'), name: 'amount', width: '50px', - style: () => { - return { color: 'var(--vn-label-color)' }; + component: 'number', + attrs: { + positive: false, }, + isEditable: false, + style: getAmountStyle, }, { align: 'center', - label: t('Package'), + label: t('Pack.'), + toolTip: t('Package'), name: 'price2', component: 'number', width: '35px', @@ -147,13 +231,40 @@ const columns = [ label: t('Box'), name: 'price3', 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', }, { align: 'center', label: 'Min.', + toolTip: t('Minimum price'), name: 'minPrice', component: 'number', + isEditable: false, width: '35px', style: (row) => { if (row?.hasMinPrice) @@ -163,21 +274,27 @@ const columns = [ { align: 'center', label: t('P.Sen'), + toolTip: t('Packing sent'), name: 'packingOut', component: 'number', + isEditable: false, width: '40px', }, { align: 'center', label: t('Com.'), + toolTip: t('Comment'), name: 'comment', component: 'input', + isEditable: false, width: '55px', }, { align: 'center', label: 'Prod.', + toolTip: t('Producer'), name: 'subName', + isEditable: false, width: '45px', style: () => { return { color: 'var(--vn-label-color)' }; @@ -185,43 +302,148 @@ const columns = [ }, { align: 'center', - label: 'Tags', + label: t('Tags'), name: 'tags', - width: '120px', + width: '125px', columnSearch: false, }, { align: 'center', label: 'Comp.', + toolTip: t('Company'), name: 'company_name', + component: 'input', + isEditable: false, 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(() => { + console.log('viewMode: ', $props.editableMode); stateStore.rightDrawer = false; }); </script> <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 ref="tableRef" data-key="EntryBuys" - :url="`Entries/${route.params.id}/getBuys`" + :url="`Entries/${entityId}/getBuyList`" + save-url="Buys/crud" :disable-option="{ card: true }" v-model:selected="selectedRows" - :table="{ - 'row-key': 'id', - selection: 'multiple', - }" + :table=" + editableMode + ? { + 'row-key': 'id', + selection: 'multiple', + } + : {} + " + :is-editable="editableMode" + :without-header="!editableMode" + :with-filters="editableMode" :right-search="false" :row-click="false" :columns="columns" class="buyList" - is-editable + table-height="84vh" auto-load + footer > - <template #column-hex> - <VnColor :colors="['#ff0000', '#ffff00', '#ff0000']" style="height: 100%" /> + <template #column-hex="{ row }"> + <VnColor :colors="row?.hexJson" style="height: 100%" /> </template> <template #column-name="{ row }"> <span class="link"> @@ -233,29 +455,57 @@ onMounted(() => { <FetchedTags :item="row" :columns="3" /> </template> <template #column-stickers="{ row }"> - <span style="color: var(--vn-label-color)">{{ row.printedStickers }}</span> - <span>/{{ row.stickers }}</span> + <span :class="editableMode ? 'editable-text' : ''"> + <span style="color: var(--vn-label-color)">{{ + row.printedStickers + }}</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> </VnTable> </template> -<style lang="scss" scoped> -.q-checkbox__inner--dark { - &__inner { - border-radius: 0% !important; - background-color: rosybrown; - } -} -</style> <i18n> es: - Article: Artículo3 - Size: Med. + Article: Artículo + Siz.: Med. + Size: Medida Sti.: Eti. Bucket: Cubo Quantity: Cantidad Amount: Importe + Pack.: Paq. Package: Paquete Box: Caja P.Sen: P.Env + Packing sent: Packing envíos 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> diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index eca78771f..3d183ad98 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -1,17 +1,17 @@ <script setup> import { ref, computed, onMounted } from 'vue'; -import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; - import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; -import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; - import { toDate } from 'src/filters'; import { usePrintService } from 'composables/usePrintService'; import { getUrl } from 'src/composables/getUrl'; 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({ id: { @@ -55,9 +55,22 @@ const getEntryRedirectionFilter = (entry) => { }); }; -const showEntryReport = () => { - openReport(`Entries/${route.params.id}/entry-order-pdf`); -}; +function showEntryReport() { + 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> <template> @@ -73,14 +86,39 @@ const showEntryReport = () => { <QItem v-ripple clickable @click="showEntryReport(entity)"> <QItemSection>{{ t('Show entry report') }}</QItemSection> </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 #body="{ entity }"> - <VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" /> - <VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" /> - <VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" /> + <VnLv :label="t('Travel')"> + <template #value> + <span class="link" v-if="entity?.travelFk"> + {{ entity.travel?.agency?.name }} + {{ entity.travel?.warehouseOut?.code }} → + {{ entity.travel?.warehouseIn?.code }} + <TravelDescriptorProxy :id="entity?.travelFk" /> + </span> + </template> + </VnLv> <VnLv - :label="t('globals.warehouseOut')" - :value="entity.travel?.warehouseOut?.name" + :label="t('entry.summary.travelShipped')" + :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 #icons="{ entity }"> @@ -107,6 +145,14 @@ const showEntryReport = () => { }}</QTooltip > </QIcon> + <QIcon + v-if="!entity?.travelFk" + name="vn:deletedTicket" + size="xs" + color="primary" + > + <QTooltip>{{ t('This entry is deleted') }}</QTooltip> + </QIcon> </QCardActions> </template> <template #actions="{ entity }"> @@ -153,6 +199,7 @@ const showEntryReport = () => { </template> <i18n> es: + Travel: Envío Supplier card: Ficha del proveedor 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 @@ -162,4 +209,5 @@ es: Virtual entry: Es una redada shipped: Enviado landed: Recibido + This entry is deleted: Esta entrada está eliminada </i18n> diff --git a/src/pages/Entry/Card/EntryFilter.js b/src/pages/Entry/Card/EntryFilter.js index 3ff62cf27..3b2a888aa 100644 --- a/src/pages/Entry/Card/EntryFilter.js +++ b/src/pages/Entry/Card/EntryFilter.js @@ -9,6 +9,7 @@ export default { 'shipped', 'agencyModeFk', 'warehouseOutFk', + 'warehouseInFk', 'daysInForward', ], include: [ @@ -21,13 +22,13 @@ export default { { relation: 'warehouseOut', scope: { - fields: ['name'], + fields: ['name', 'code'], }, }, { relation: 'warehouseIn', scope: { - fields: ['name'], + fields: ['name', 'code'], }, }, ], @@ -39,5 +40,11 @@ export default { fields: ['id', 'nickname'], }, }, + { + relation: 'currency', + scope: { + fields: ['id', 'code'], + }, + }, ], }; diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index a8091fba2..8caa9acf5 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -2,15 +2,15 @@ import { onMounted, ref, computed } from 'vue'; import { useRoute } from 'vue-router'; 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 VnLv from 'src/components/ui/VnLv.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; - -import { toDate, toCurrency } from 'src/filters'; -import { getUrl } from 'src/composables/getUrl'; -import axios from 'axios'; -import FetchedTags from 'src/components/ui/FetchedTags.vue'; +import EntryBuys from './EntryBuys.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; const route = useRoute(); @@ -30,117 +30,6 @@ const entry = ref(); const entryBuys = 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) { if (data) entry.value = data; await fetchEntryBuys(); @@ -150,8 +39,11 @@ const fetchEntryBuys = async () => { const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`); if (data) entryBuys.value = data; }; -</script> +onMounted(async () => { + entryUrl.value = (await getUrl('entry/')) + entityId.value; +}); +</script> <template> <CardSummary ref="summaryRef" @@ -168,34 +60,64 @@ const fetchEntryBuys = async () => { /> </template> <template #header> - <span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> + <span>{{ entry.id }} - {{ entry?.supplier?.nickname }}</span> </template> <template #body> <QCard class="vn-one"> - <router-link - :to="{ name: 'EntryBasicData', params: { id: entityId } }" - class="header header-link" - > - {{ t('globals.summary.basicData') }} - <QIcon name="open_in_new" /> - </router-link> + <VnTitle + :url="`#/entry/{{ entityId }}/basicData`" + :text="t('globals.summary.basicData')" + /> <div class="card-group"> <div class="card-content"> <VnLv :label="t('entry.summary.commission')" - :value="entry.commission" + :value="entry?.commission" /> <VnLv :label="t('entry.summary.currency')" - :value="entry.currency?.name" + :value="entry?.currency?.name" /> - <VnLv :label="t('globals.company')" :value="entry.company.code" /> - <VnLv :label="t('globals.reference')" :value="entry.reference" /> + <VnLv + :label="t('globals.company')" + :value="entry?.company?.code" + /> + <VnLv :label="t('globals.reference')" :value="entry?.reference" /> <VnLv :label="t('entry.summary.invoiceNumber')" - :value="entry.invoiceNumber" + :value="entry?.invoiceNumber" /> </div> + <div class="card-content"> + <QCheckbox + :label="t('entry.summary.ordered')" + v-model="entry.isOrdered" + :disable="true" + /> + <QCheckbox + :label="t('globals.confirmed')" + v-model="entry.isConfirmed" + :disable="true" + /> + <QCheckbox + :label="t('entry.summary.booked')" + v-model="entry.isBooked" + :disable="true" + /> + <QCheckbox + :label="t('entry.summary.excludedFromAvailable')" + v-model="entry.isExcludedFromAvailable" + :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> @@ -210,18 +132,23 @@ const fetchEntryBuys = async () => { :value="entry.travel.agency?.name" /> <VnLv - :label="t('shipped')" + :label="t('entry.summary.travelShipped')" :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('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" @@ -235,59 +162,35 @@ const fetchEntryBuys = async () => { </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 - :label="t('entry.summary.ordered')" - v-model="entry.isOrdered" - :disable="true" - /> - <QCheckbox - :label="t('globals.confirmed')" - v-model="entry.isConfirmed" - :disable="true" - /> - <QCheckbox - :label="t('entry.summary.booked')" - v-model="entry.isBooked" - :disable="true" - /> - <QCheckbox - :label="t('entry.summary.excludedFromAvailable')" - v-model="entry.isExcludedFromAvailable" - :disable="true" + <QCard class="vn-max"> + <VnTitle + :url="`#/entry/{{ entityId }}/buys`" + :text="t('entry.summary.buys')" /> + <EntryBuys v-if="entityId" :id="entityId" :editable-mode="false" /> </QCard> </template> </CardSummary> </template> - <style lang="scss" scoped> -.separation-row { - background-color: var(--vn-section-color) !important; -} .card-group { display: flex; flex-direction: column; } .card-content { - margin-bottom: 16px; /* Para dar espacio entre las secciones */ + display: flex; + flex-direction: column; + text-overflow: ellipsis; } @media (min-width: 1010px) { .card-group { - flex-direction: row; /* Coloca los contenidos en fila cuando el ancho es mayor a 600px */ + flex-direction: row; } .card-content { - flex: 1; /* Haz que las secciones ocupen el mismo espacio */ - margin-right: 16px; /* Espaciado entre las dos primeras tarjetas */ + flex: 1; + margin-right: 16px; } } </style> @@ -295,4 +198,5 @@ const fetchEntryBuys = async () => { <i18n> es: Travel data: Datos envío + InvoiceIn data: Datos factura </i18n> diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index f50810eb7..3cad020ed 100644 --- a/src/pages/Entry/EntryFilter.vue +++ b/src/pages/Entry/EntryFilter.vue @@ -18,6 +18,7 @@ const props = defineProps({ const currenciesOptions = ref([]); const companiesOptions = ref([]); +const entryFilterPanel = ref(); </script> <template> @@ -37,7 +38,7 @@ const companiesOptions = ref([]); @on-fetch="(data) => (currenciesOptions = data)" auto-load /> - <VnFilterPanel :data-key="props.dataKey" :search-button="true"> + <VnFilterPanel ref="entryFilterPanel" :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`params.${tag.label}`) }}: </strong> @@ -47,70 +48,82 @@ const companiesOptions = ref([]); <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInput - v-model="params.search" - :label="t('entryFilter.filter.search')" - is-outlined - /> + <QCheckbox + :label="t('params.isExcludedFromAvailable')" + v-model="params.isExcludedFromAvailable" + 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> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.reference" - :label="t('entryFilter.filter.reference')" - is-outlined - /> + <QCheckbox + :label="t('params.isReceived')" + v-model="params.isReceived" + 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> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.invoiceNumber" - :label="t('params.invoiceNumber')" - is-outlined - /> + <QCheckbox + :label="t('entry.list.tableVisibleColumns.isConfirmed')" + v-model="params.isConfirmed" + toggle-indeterminate + > + <QTooltip> + {{ t('entry.list.tableVisibleColumns.isConfirmed') }} + </QTooltip> + </QCheckbox> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInput - v-model="params.travelFk" - :label="t('params.travelFk')" - is-outlined - /> - </QItemSection> - </QItem> - <QItem> - <QItemSection> - <VnSelect - :label="t('params.companyFk')" - v-model="params.companyFk" + <VnInputDate + :label="t('params.landed')" + v-model="params.landed" @update:model-value="searchFn()" - :options="companiesOptions" - option-value="id" - option-label="code" - hide-selected - dense - outlined - rounded + is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnSelect - :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 - /> + <VnInput v-model="params.id" label="Id" is-outlined /> </QItemSection> </QItem> <QItem> @@ -143,56 +156,90 @@ const companiesOptions = ref([]); </QItem> <QItem> <QItemSection> - <VnInputDate - :label="t('params.created')" - v-model="params.created" - @update:model-value="searchFn()" + <VnInput + v-model="params.invoiceNumber" + :label="t('params.invoiceNumber')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - :label="t('params.from')" - v-model="params.from" - @update:model-value="searchFn()" + <VnInput + v-model="params.reference" + :label="t('entryFilter.filter.reference')" is-outlined /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate - :label="t('params.to')" - v-model="params.to" + <VnSelect + :label="t('params.agencyModeId')" + v-model="params.agencyModeId" @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 /> </QItemSection> </QItem> <QItem> <QItemSection> - <QCheckbox - :label="t('params.isBooked')" - v-model="params.isBooked" - toggle-indeterminate - /> - </QItemSection> - <QItemSection> - <QCheckbox - :label="t('params.isConfirmed')" - v-model="params.isConfirmed" - toggle-indeterminate + <VnSelect + :label="t('params.warehouseOutFk')" + v-model="params.warehouseOutFk" + @update:model-value="searchFn()" + url="Warehouses" + :fields="['id', 'name']" + hide-selected + dense + outlined + rounded /> </QItemSection> </QItem> <QItem> <QItemSection> - <QCheckbox - :label="t('params.isOrdered')" - v-model="params.isOrdered" - toggle-indeterminate + <VnSelect + :label="t('params.warehouseInFk')" + v-model="params.warehouseInFk" + @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> </QItem> @@ -203,30 +250,38 @@ const companiesOptions = ref([]); <i18n> en: params: - - invoiceNumber: Invoice number - travelFk: Travel - companyFk: Company - currencyFk: Currency - supplierFk: Supplier - from: From - to: To - created: Created - isBooked: Booked - isConfirmed: Confirmed + isExcludedFromAvailable: Inventory 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: params: - - 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 + isExcludedFromAvailable: Inventario 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> diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index ff79cf685..6cb912ca7 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -1,23 +1,24 @@ <script setup> -import { onMounted, ref, computed } from 'vue'; +import axios from 'axios'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; +import { useState } from 'src/composables/useState'; +import { onBeforeMount } from 'vue'; + import EntryFilter from './EntryFilter.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; -import { useStateStore } from 'stores/useStateStore'; import VnTable from 'components/VnTable/VnTable.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; 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 TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; -const stateStore = useStateStore(); const { t } = useI18n(); const tableRef = ref(); +const defaultEntry = ref({}); +const state = useState(); +const user = state.getUser(); -const { viewSummary } = useSummaryDialog(); -const entryFilter = { +const entryQueryFilter = { include: [ { relation: 'suppliers', @@ -42,11 +43,50 @@ const entryFilter = { const columns = computed(() => [ { - name: 'status', - columnFilter: false, + align: 'center', + 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'), name: 'id', isId: true, @@ -54,30 +94,6 @@ const columns = computed(() => [ 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', label: t('entry.list.tableVisibleColumns.supplierFk'), @@ -89,117 +105,168 @@ const columns = computed(() => [ url: 'suppliers', fields: ['id', 'name'], }, - columnField: { - component: null, - }, format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), }, { - align: 'center', - label: t('entry.list.tableVisibleColumns.isBooked'), - name: 'isBooked', - cardVisible: true, - 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', + label: t('entry.list.tableVisibleColumns.invoiceNumber'), + name: 'invoiceNumber', + component: 'input', }, { align: 'left', - label: t('entry.list.tableVisibleColumns.companyFk'), - name: '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: 'companies', - fields: ['id', 'code'], - optionLabel: 'code', - optionValue: 'id', + url: 'agencyModes', + fields: ['id', 'name'], }, columnField: { component: null, }, - create: true, - - format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode), + format: (row, dashIfEmpty) => dashIfEmpty(row.agencyModeName), }, { align: 'left', - label: t('entry.list.tableVisibleColumns.travelFk'), - name: 'travelFk', + 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: 'travels', - fields: ['id', 'ref'], - optionLabel: 'ref', - optionValue: 'id', + url: 'warehouses', + fields: ['id', 'name'], }, columnField: { component: null, }, - create: true, - format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), + format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseOutName), }, { align: 'left', - label: t('entry.list.tableVisibleColumns.invoiceAmount'), - name: 'invoiceAmount', + 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: 'center', - label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), - name: 'isExcludedFromAvailable', - chip: { - color: null, - condition: (value) => value, - icon: 'vn:inventory', - }, + align: 'left', + label: t('entry.list.tableVisibleColumns.entryTypeDescription'), + name: 'entryTypeCode', + cardVisible: true, 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, + 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', + label: t('entry.list.tableVisibleColumns.companyFk'), + cardVisible: false, + visible: false, + create: true, + component: 'select', + attrs: { + optionValue: 'id', + optionLabel: 'code', + url: 'Companies', + }, + }, + { + name: 'travelFk', + label: t('entry.list.tableVisibleColumns.travelFk'), + cardVisible: false, + visible: false, + create: 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> <template> <VnSearchbar @@ -214,40 +281,35 @@ const columns = computed(() => [ </template> </RightMenu> <VnTable + v-if="defaultEntry.defaultSupplierFk" ref="tableRef" data-key="EntryList" url="Entries/filter" - :filter="entryFilter" + :filter="entryQueryFilter" :create="{ urlCreate: 'Entries', title: t('Create entry'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, + formInitialData: { + supplierFk: defaultEntry.defaultSupplierFk, + dated: Date.vnNew(), + companyFk: user?.companyFk, + }, }" order="id DESC" :columns="columns" redirect="entry" :right-search="false" > - <template #column-status="{ row }"> - <div class="row q-gutter-xs"> - <QIcon - v-if="!!row.isExcludedFromAvailable" - name="vn:inventory" - color="primary" - > - <QTooltip>{{ - t('entry.list.tableVisibleColumns.isExcludedFromAvailable') - }}</QTooltip> - </QIcon> - <QIcon v-if="!!row.isRaid" name="vn:net" color="primary"> - <QTooltip> - {{ - t('globals.raid', { daysInForward: row.daysInForward }) - }}</QTooltip - > - </QIcon> - </div> + <template #column-landed="{ row }"> + <QBadge + v-if="row?.travelFk" + v-bind="getBadgeAttrs(row)" + class="q-pa-sm" + style="font-size: 14px" + > + {{ toDate(row.landed) }} + </QBadge> </template> <template #column-supplierFk="{ row }"> <span class="link" @click.stop> @@ -255,12 +317,6 @@ const columns = computed(() => [ <SupplierDescriptorProxy :id="row.supplierFk" /> </span> </template> - <template #column-travelFk="{ row }"> - <span class="link" @click.stop> - {{ row.travelRef }} - <TravelDescriptorProxy :id="row.travelFk" /> - </span> - </template> </VnTable> </template> diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue index 4705525fb..a51d76e9c 100644 --- a/src/pages/Item/Card/ItemDescriptor.vue +++ b/src/pages/Item/Card/ItemDescriptor.vue @@ -36,6 +36,10 @@ const $props = defineProps({ type: Number, default: null, }, + proxyRender: { + type: Boolean, + default: false, + }, }); const { openCloneDialog } = cloneItem(); @@ -171,7 +175,7 @@ const openRegularizeStockForm = () => { </QCardActions> </template> <template #actions="{}"> - <QCardActions class="row justify-center"> + <QCardActions class="row justify-center" v-if="proxyRender"> <QBtn :to="{ name: 'ItemDiary', @@ -184,6 +188,16 @@ const openRegularizeStockForm = () => { > <QTooltip>{{ t('item.descriptor.itemDiary') }}</QTooltip> </QBtn> + <QBtn + :to="{ + name: 'ItemLastEntries', + }" + size="md" + icon="vn:regentry" + color="primary" + > + <QTooltip>{{ t('item.descriptor.itemLastEntries') }}</QTooltip> + </QBtn> </QCardActions> </template> </CardDescriptor> diff --git a/src/pages/Item/Card/ItemDescriptorProxy.vue b/src/pages/Item/Card/ItemDescriptorProxy.vue index 3891c9f17..f686e8221 100644 --- a/src/pages/Item/Card/ItemDescriptorProxy.vue +++ b/src/pages/Item/Card/ItemDescriptorProxy.vue @@ -4,7 +4,7 @@ import ItemSummary from './ItemSummary.vue'; const $props = defineProps({ id: { - type: Number, + type: [Number, String], required: true, }, dated: { @@ -30,6 +30,7 @@ const $props = defineProps({ :dated="dated" :sale-fk="saleFk" :warehouse-fk="warehouseFk" + :proxy-render="true" /> </QPopupProxy> </template> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index ac5010a12..42961db3d 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -117,6 +117,7 @@ item: available: Available warehouseText: 'Calculated on the warehouse of { warehouseName }' itemDiary: Item diary + itemLastEntries: Last entries producer: Producer clone: title: All its properties will be copied diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 96c8cbc9a..d4dd30123 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -119,6 +119,7 @@ item: available: Disponible warehouseText: 'Calculado sobre el almacén de { warehouseName }' itemDiary: Registro de compra-venta + itemLastEntries: Últimas entradas producer: Productor clone: title: Todas sus propiedades serán copiadas