Merge branch 'test' into 7356_ticketService
gitea/salix-front/pipeline/pr-test There was a failure building this commit Details

This commit is contained in:
Javier Segarra 2025-03-01 01:41:22 +00:00
commit ba98816f93
45 changed files with 564 additions and 319 deletions

View File

@ -95,6 +95,10 @@ const $props = defineProps({
type: [String, Boolean], type: [String, Boolean],
default: '800px', default: '800px',
}, },
onDataSaved: {
type: Function,
default: () => {},
},
}); });
const emit = defineEmits(['onFetch', 'onDataSaved']); const emit = defineEmits(['onFetch', 'onDataSaved']);
const modelValue = computed( const modelValue = computed(

View File

@ -1,12 +1,13 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, useAttrs, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
const emit = defineEmits(['onDataSaved', 'onDataCanceled']); const emit = defineEmits(['onDataSaved', 'onDataCanceled']);
defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
default: '', default: '',
@ -22,12 +23,21 @@ defineProps({
}); });
const { t } = useI18n(); const { t } = useI18n();
const attrs = useAttrs();
const state = useState();
const formModelRef = ref(null); const formModelRef = ref(null);
const closeButton = ref(null); const closeButton = ref(null);
const isSaveAndContinue = ref(false); const isSaveAndContinue = ref(props.showSaveAndContinueBtn);
const onDataSaved = (formData, requestResponse) => { const isLoading = computed(() => formModelRef.value?.isLoading);
if (closeButton.value && !isSaveAndContinue.value) closeButton.value.click(); const reset = computed(() => formModelRef.value?.reset);
const onDataSaved = async (formData, requestResponse) => {
if (!isSaveAndContinue.value) closeButton.value?.click();
if (isSaveAndContinue.value) {
await nextTick();
state.set(attrs.model, attrs.formInitialData);
}
isSaveAndContinue.value = props.showSaveAndContinueBtn;
emit('onDataSaved', formData, requestResponse); emit('onDataSaved', formData, requestResponse);
}; };
@ -36,9 +46,6 @@ const onClick = async (saveAndContinue) => {
await formModelRef.value.save(); await formModelRef.value.save();
}; };
const isLoading = computed(() => formModelRef.value?.isLoading);
const reset = computed(() => formModelRef.value?.reset);
defineExpose({ defineExpose({
isLoading, isLoading,
onDataSaved, onDataSaved,
@ -74,10 +81,7 @@ defineExpose({
data-cy="FormModelPopup_cancel" data-cy="FormModelPopup_cancel"
v-close-popup v-close-popup
z-max z-max
@click=" @click="emit('onDataCanceled')"
isSaveAndContinue = false;
emit('onDataCanceled');
"
/> />
<QBtn <QBtn
:flat="showSaveAndContinueBtn" :flat="showSaveAndContinueBtn"

View File

@ -91,7 +91,6 @@ const components = {
event: updateEvent, event: updateEvent,
attrs: { attrs: {
...defaultAttrs, ...defaultAttrs,
style: 'min-width: 150px',
}, },
forceAttrs, forceAttrs,
}, },

View File

@ -31,6 +31,7 @@ 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 { getColAlign } from 'src/composables/getColAlign'; import { getColAlign } from 'src/composables/getColAlign';
import RightMenu from '../common/RightMenu.vue';
const arrayData = useArrayData(useAttrs()['data-key']); const arrayData = useArrayData(useAttrs()['data-key']);
const $props = defineProps({ const $props = defineProps({
@ -137,6 +138,10 @@ const $props = defineProps({
createComplement: { createComplement: {
type: Object, type: Object,
}, },
dataCy: {
type: String,
default: 'vn-table',
},
}); });
const { t } = useI18n(); const { t } = useI18n();
@ -165,7 +170,6 @@ const app = inject('app');
const editingRow = ref(null); const editingRow = ref(null);
const editingField = ref(null); const editingField = ref(null);
const isTableMode = computed(() => mode.value == TABLE_MODE); const isTableMode = computed(() => mode.value == TABLE_MODE);
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
const selectRegex = /select/; const selectRegex = /select/;
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
const tableModes = [ const tableModes = [
@ -253,7 +257,9 @@ function splitColumns(columns) {
col.columnFilter = { inWhere: true, ...col.columnFilter }; col.columnFilter = { inWhere: true, ...col.columnFilter };
splittedColumns.value.columns.push(col); splittedColumns.value.columns.push(col);
} }
// Status column
splittedColumns.value.create = createOrderSort(splittedColumns.value.create);
if (splittedColumns.value.chips.length) { if (splittedColumns.value.chips.length) {
splittedColumns.value.columnChips = splittedColumns.value.chips.filter( splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
(c) => !c.isId, (c) => !c.isId,
@ -269,6 +275,24 @@ function splitColumns(columns) {
} }
} }
function createOrderSort(columns) {
const orderedColumn = columns
.map((column, index) =>
column.createOrder !== undefined ? { ...column, originalIndex: index } : null,
)
.filter((item) => item !== null);
orderedColumn.sort((a, b) => a.createOrder - b.createOrder);
const filteredColumns = columns.filter((col) => col.createOrder === undefined);
orderedColumn.forEach((col) => {
filteredColumns.splice(col.createOrder, 0, col);
});
return filteredColumns;
}
const rowClickFunction = computed(() => { const rowClickFunction = computed(() => {
if ($props.rowClick != undefined) return $props.rowClick; if ($props.rowClick != undefined) return $props.rowClick;
if ($props.redirect) return ({ id }) => redirectFn(id); if ($props.redirect) return ({ id }) => redirectFn(id);
@ -314,8 +338,14 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
if (evt?.shiftKey && added) { if (evt?.shiftKey && added) {
const rowIndex = selectedRows[0].$index; const rowIndex = selectedRows[0].$index;
const selectedIndexes = new Set(selected.value.map((row) => row.$index)); const selectedIndexes = new Set(selected.value.map((row) => row.$index));
for (const row of rows) { const minIndex = selectedIndexes.size
if (row.$index == rowIndex) break; ? Math.min(...selectedIndexes, rowIndex)
: 0;
const maxIndex = Math.max(...selectedIndexes, rowIndex);
for (let i = minIndex; i <= maxIndex; i++) {
const row = rows[i];
if (row.$index == rowIndex) continue;
if (!selectedIndexes.has(row.$index)) { if (!selectedIndexes.has(row.$index)) {
selected.value.push(row); selected.value.push(row);
selectedIndexes.add(row.$index); selectedIndexes.add(row.$index);
@ -338,12 +368,11 @@ function hasEditableFormat(column) {
const clickHandler = async (event) => { const clickHandler = async (event) => {
const clickedElement = event.target.closest('td'); const clickedElement = event.target.closest('td');
const isDateElement = event.target.closest('.q-date'); const isDateElement = event.target.closest('.q-date');
const isTimeElement = event.target.closest('.q-time'); const isTimeElement = event.target.closest('.q-time');
const isQselectDropDown = event.target.closest('.q-select__dropdown-icon'); const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon');
if (isDateElement || isTimeElement || isQselectDropDown) return; if (isDateElement || isTimeElement || isQSelectDropDown) return;
if (clickedElement === null) { if (clickedElement === null) {
await destroyInput(editingRow.value, editingField.value); await destroyInput(editingRow.value, editingField.value);
@ -414,20 +443,13 @@ async function renderInput(rowId, field, clickedElement) {
eventHandlers: { eventHandlers: {
'update:modelValue': async (value) => { 'update:modelValue': async (value) => {
if (isSelect && value) { if (isSelect && value) {
row[column.name] = value[column.attrs?.optionValue ?? 'id']; await updateSelectValue(value, column, row, oldValue);
row[column?.name + 'TextValue'] =
value[column.attrs?.optionLabel ?? 'name'];
await column?.cellEvent?.['update:modelValue']?.(
value,
oldValue,
row,
);
} else row[column.name] = value; } else row[column.name] = value;
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row); await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
}, },
keyup: async (event) => { keyup: async (event) => {
if (event.key === 'Enter') if (event.key === 'Enter')
await destroyInput(rowIndex, field, clickedElement); await destroyInput(rowId, field, clickedElement);
}, },
keydown: async (event) => { keydown: async (event) => {
switch (event.key) { switch (event.key) {
@ -458,6 +480,17 @@ async function renderInput(rowId, field, clickedElement) {
node.el?.querySelector('span > div > div').focus(); node.el?.querySelector('span > div > div').focus();
} }
async function updateSelectValue(value, column, row, oldValue) {
row[column.name] = value[column.attrs?.optionValue ?? 'id'];
row[column?.name + 'VnTableTextValue'] = value[column.attrs?.optionLabel ?? 'name'];
if (column?.attrs?.find?.label)
row[column?.attrs?.find?.label] = value[column.attrs?.optionLabel ?? 'name'];
await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row);
}
async function destroyInput(rowIndex, field, clickedElement) { async function destroyInput(rowIndex, field, clickedElement) {
if (!clickedElement) if (!clickedElement)
clickedElement = document.querySelector( clickedElement = document.querySelector(
@ -520,9 +553,9 @@ function getToggleIcon(value) {
} }
function formatColumnValue(col, row, dashIfEmpty) { function formatColumnValue(col, row, dashIfEmpty) {
if (col?.format || row[col?.name + 'TextValue']) { if (col?.format || row[col?.name + 'VnTableTextValue']) {
if (selectRegex.test(col?.component) && row[col?.name + 'TextValue']) { if (selectRegex.test(col?.component) && row[col?.name + 'VnTableTextValue']) {
return dashIfEmpty(row[col?.name + 'TextValue']); return dashIfEmpty(row[col?.name + 'VnTableTextValue']);
} else { } else {
return col.format(row, dashIfEmpty); return col.format(row, dashIfEmpty);
} }
@ -555,19 +588,48 @@ function formatColumnValue(col, row, dashIfEmpty) {
} }
return dashIfEmpty(row[col?.name]); return dashIfEmpty(row[col?.name]);
} }
function cardClick(_, row) { function cardClick(_, row) {
if ($props.redirect) router.push({ path: `/${$props.redirect}/${row.id}` }); if ($props.redirect) router.push({ path: `/${$props.redirect}/${row.id}` });
} }
function removeTextValue(data, getChanges) {
let changes = data.updates;
if (!changes) return data;
for (const change of changes) {
for (const key in change.data) {
if (key.endsWith('VnTableTextValue')) {
delete change.data[key];
}
}
}
data.updates = changes.filter((change) => Object.keys(change.data).length > 0);
if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges);
return data;
}
function handleRowClick(event, row) {
if (event.ctrlKey) return rowCtrlClickFunction.value(event, row);
if (rowClickFunction.value) rowClickFunction.value(row);
}
const rowCtrlClickFunction = computed(() => {
if ($props.rowCtrlClick != undefined) return $props.rowCtrlClick;
if ($props.redirect)
return (evt, { id }) => {
stopEventPropagation(evt);
window.open(`/#/${$props.redirect}/${id}`, '_blank');
};
return () => {};
});
</script> </script>
<template> <template>
<QDrawer <RightMenu v-if="$props.rightSearch" :overlay="overlay">
v-if="$props.rightSearch" <template #right-panel>
v-model="stateStore.rightDrawer"
side="right"
:width="256"
:overlay="$props.overlay"
>
<QScrollArea class="fit">
<VnTableFilter <VnTableFilter
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
:columns="columns" :columns="columns"
@ -581,8 +643,8 @@ function cardClick(_, row) {
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template> </template>
</VnTableFilter> </VnTableFilter>
</QScrollArea> </template>
</QDrawer> </RightMenu>
<CrudModel <CrudModel
v-bind="$attrs" v-bind="$attrs"
:class="$attrs['class'] ?? 'q-px-md'" :class="$attrs['class'] ?? 'q-px-md'"
@ -591,6 +653,7 @@ function cardClick(_, row) {
@on-fetch="(...args) => emit('onFetch', ...args)" @on-fetch="(...args) => emit('onFetch', ...args)"
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="isTableMode" :disable-infinite-scroll="isTableMode"
:before-save-fn="removeTextValue"
@save-changes="reload" @save-changes="reload"
:has-sub-toolbar="$props.hasSubToolbar ?? isEditable" :has-sub-toolbar="$props.hasSubToolbar ?? isEditable"
:auto-load="hasParams || $attrs['auto-load']" :auto-load="hasParams || $attrs['auto-load']"
@ -618,7 +681,7 @@ function cardClick(_, row) {
:style="isTableMode && `max-height: ${tableHeight}`" :style="isTableMode && `max-height: ${tableHeight}`"
:virtual-scroll="isTableMode" :virtual-scroll="isTableMode"
@virtual-scroll="handleScroll" @virtual-scroll="handleScroll"
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)" @row-click="(event, row) => handleRowClick(event, row)"
@update:selected="emit('update:selected', $event)" @update:selected="emit('update:selected', $event)"
@selection="(details) => handleSelection(details, rows)" @selection="(details) => handleSelection(details, rows)"
:hide-selected-banner="true" :hide-selected-banner="true"
@ -635,20 +698,13 @@ function cardClick(_, row) {
:skip="columnsVisibilitySkipped" :skip="columnsVisibilitySkipped"
/> />
<QBtnToggle <QBtnToggle
v-if="!tableModes.some((mode) => mode.disable)"
v-model="mode" v-model="mode"
toggle-color="primary" toggle-color="primary"
class="bg-vn-section-color" class="bg-vn-section-color"
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
@ -961,14 +1017,6 @@ function cardClick(_, row) {
transition-show="scale" transition-show="scale"
transition-hide="scale" transition-hide="scale"
:full-width="createComplement?.isFullWidth ?? false" :full-width="createComplement?.isFullWidth ?? false"
@before-hide="
() => {
if (createRef.isSaveAndContinue) {
showForm = true;
createForm.formInitialData = { ...create.formInitialData };
}
}
"
data-cy="vn-table-create-dialog" data-cy="vn-table-create-dialog"
> >
<FormModelPopup <FormModelPopup
@ -979,7 +1027,10 @@ function cardClick(_, row) {
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<div :style="createComplement?.containerStyle"> <div :style="createComplement?.containerStyle">
<div> <div
:style="createComplement?.previousStyle"
v-if="!quasar.screen.xs"
>
<slot name="previous-create-dialog" :data="data" /> <slot name="previous-create-dialog" :data="data" />
</div> </div>
<div class="grid-create" :style="createComplement?.columnGridStyle"> <div class="grid-create" :style="createComplement?.columnGridStyle">
@ -992,7 +1043,10 @@ function cardClick(_, row) {
:label="column.label" :label="column.label"
> >
<VnColumn <VnColumn
:column="column" :column="{
...column,
...{ disable: column?.createDisable ?? false },
}"
:row="{}" :row="{}"
default="input" default="input"
v-model="data[column.name]" v-model="data[column.name]"

View File

@ -27,30 +27,58 @@ describe('VnTable', () => {
beforeEach(() => (vm.selected = [])); beforeEach(() => (vm.selected = []));
describe('handleSelection()', () => { describe('handleSelection()', () => {
const rows = [{ $index: 0 }, { $index: 1 }, { $index: 2 }]; const rows = [
const selectedRows = [{ $index: 1 }]; { $index: 0 },
it('should add rows to selected when shift key is pressed and rows are added except last one', () => { { $index: 1 },
{ $index: 2 },
{ $index: 3 },
{ $index: 4 },
];
it('should add rows to selected when shift key is pressed and rows are added in ascending order', () => {
const selectedRows = [{ $index: 1 }];
vm.handleSelection( vm.handleSelection(
{ evt: { shiftKey: true }, added: true, rows: selectedRows }, { evt: { shiftKey: true }, added: true, rows: selectedRows },
rows rows,
); );
expect(vm.selected).toEqual([{ $index: 0 }]); expect(vm.selected).toEqual([{ $index: 0 }]);
}); });
it('should add rows to selected when shift key is pressed and rows are added in descending order', () => {
const selectedRows = [{ $index: 3 }];
vm.handleSelection(
{ evt: { shiftKey: true }, added: true, rows: selectedRows },
rows,
);
expect(vm.selected).toEqual([{ $index: 0 }, { $index: 1 }, { $index: 2 }]);
});
it('should not add rows to selected when shift key is not pressed', () => { it('should not add rows to selected when shift key is not pressed', () => {
const selectedRows = [{ $index: 1 }];
vm.handleSelection( vm.handleSelection(
{ evt: { shiftKey: false }, added: true, rows: selectedRows }, { evt: { shiftKey: false }, added: true, rows: selectedRows },
rows rows,
); );
expect(vm.selected).toEqual([]); expect(vm.selected).toEqual([]);
}); });
it('should not add rows to selected when rows are not added', () => { it('should not add rows to selected when rows are not added', () => {
const selectedRows = [{ $index: 1 }];
vm.handleSelection( vm.handleSelection(
{ evt: { shiftKey: true }, added: false, rows: selectedRows }, { evt: { shiftKey: true }, added: false, rows: selectedRows },
rows rows,
); );
expect(vm.selected).toEqual([]); expect(vm.selected).toEqual([]);
}); });
it('should add all rows between the smallest and largest selected indexes', () => {
vm.selected = [{ $index: 1 }, { $index: 3 }];
const selectedRows = [{ $index: 4 }];
vm.handleSelection(
{ evt: { shiftKey: true }, added: true, rows: selectedRows },
rows,
);
expect(vm.selected).toEqual([{ $index: 1 }, { $index: 3 }, { $index: 2 }]);
});
}); });
}); });

View File

@ -11,6 +11,13 @@ const stateStore = useStateStore();
const slots = useSlots(); const slots = useSlots();
const hasContent = useHasContent('#right-panel'); const hasContent = useHasContent('#right-panel');
defineProps({
overlay: {
type: Boolean,
default: false,
},
});
onMounted(() => { onMounted(() => {
if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile) if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile)
stateStore.rightDrawer = false; stateStore.rightDrawer = false;
@ -34,7 +41,12 @@ onMounted(() => {
</QBtn> </QBtn>
</div> </div>
</Teleport> </Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256"> <QDrawer
v-model="stateStore.rightDrawer"
side="right"
:width="256"
:overlay="overlay"
>
<QScrollArea class="fit"> <QScrollArea class="fit">
<div id="right-panel"></div> <div id="right-panel"></div>
<slot v-if="!hasContent" name="right-panel" /> <slot v-if="!hasContent" name="right-panel" />

View File

@ -27,7 +27,7 @@ const checkboxModel = computed({
</script> </script>
<template> <template>
<div> <div>
<QCheckbox v-bind="$attrs" v-on="$attrs" v-model="checkboxModel" /> <QCheckbox v-bind="$attrs" v-model="checkboxModel" />
<QIcon <QIcon
v-if="info" v-if="info"
v-bind="$attrs" v-bind="$attrs"

View File

@ -302,8 +302,6 @@ defineExpose({ opts: myOptions, vnSelectRef });
function handleKeyDown(event) { function handleKeyDown(event) {
if (event.key === 'Tab' && !event.shiftKey) { if (event.key === 'Tab' && !event.shiftKey) {
event.preventDefault();
const inputValue = vnSelectRef.value?.inputValue; const inputValue = vnSelectRef.value?.inputValue;
if (inputValue) { if (inputValue) {

View File

@ -76,6 +76,15 @@ onBeforeMount(async () => {
); );
}); });
const routeName = computed(() => {
const DESCRIPTOR_PROXY = 'DescriptorProxy';
let name = $props.dataKey;
if ($props.dataKey.includes(DESCRIPTOR_PROXY)) {
name = name.split(DESCRIPTOR_PROXY)[0];
}
return `${name}Summary`;
});
async function getData() { async function getData() {
store.url = $props.url; store.url = $props.url;
store.filter = $props.filter ?? {}; store.filter = $props.filter ?? {};
@ -154,9 +163,7 @@ const toModule = computed(() =>
{{ t('components.smartCard.openSummary') }} {{ t('components.smartCard.openSummary') }}
</QTooltip> </QTooltip>
</QBtn> </QBtn>
<RouterLink <RouterLink :to="{ name: routeName, params: { id: entity.id } }">
:to="{ name: `${dataKey}Summary`, params: { id: entity.id } }"
>
<QBtn <QBtn
class="link" class="link"
color="white" color="white"

View File

@ -204,8 +204,9 @@ async function search() {
} }
:deep(.q-field--focused) { :deep(.q-field--focused) {
.q-icon { .q-icon,
color: black; .q-placeholder {
color: var(--vn-black-text-color);
} }
} }

View File

@ -29,7 +29,6 @@ export async function checkEntryLock(entryFk, userFk) {
.dialog({ .dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
'data-cy': 'entry-lock-confirm',
title: t('entry.lock.title'), title: t('entry.lock.title'),
message: t('entry.lock.message', { message: t('entry.lock.message', {
userName: data?.user?.nickname, userName: data?.user?.nickname,

View File

@ -153,6 +153,7 @@ globals:
maxTemperature: Max maxTemperature: Max
minTemperature: Min minTemperature: Min
changePass: Change password changePass: Change password
setPass: Set password
deleteConfirmTitle: Delete selected elements deleteConfirmTitle: Delete selected elements
changeState: Change state changeState: Change state
raid: 'Raid {daysInForward} days' raid: 'Raid {daysInForward} days'
@ -693,8 +694,10 @@ worker:
machine: Machine machine: Machine
business: business:
tableVisibleColumns: tableVisibleColumns:
id: ID
started: Start Date started: Start Date
ended: End Date ended: End Date
hourlyLabor: Time sheet
company: Company company: Company
reasonEnd: Reason for Termination reasonEnd: Reason for Termination
department: Department department: Department
@ -702,6 +705,7 @@ worker:
calendarType: Work Calendar calendarType: Work Calendar
workCenter: Work Center workCenter: Work Center
payrollCategories: Contract Category payrollCategories: Contract Category
workerBusinessAgreementName: Agreement
occupationCode: Contribution Code occupationCode: Contribution Code
rate: Rate rate: Rate
businessType: Contract Type businessType: Contract Type

View File

@ -157,6 +157,7 @@ globals:
maxTemperature: Máx maxTemperature: Máx
minTemperature: Mín minTemperature: Mín
changePass: Cambiar contraseña changePass: Cambiar contraseña
setPass: Establecer contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado changeState: Cambiar estado
raid: 'Redada {daysInForward} días' raid: 'Redada {daysInForward} días'
@ -769,8 +770,10 @@ worker:
concept: Concepto concept: Concepto
business: business:
tableVisibleColumns: tableVisibleColumns:
id: Id
started: Fecha inicio started: Fecha inicio
ended: Fecha fin ended: Fecha fin
hourlyLabor: Ficha
company: Empresa company: Empresa
reasonEnd: Motivo finalización reasonEnd: Motivo finalización
department: Departamento department: Departamento
@ -781,6 +784,7 @@ worker:
occupationCode: Cotización occupationCode: Cotización
rate: Tarifa rate: Tarifa
businessType: Contrato businessType: Contrato
workerBusinessAgreementName: Convenio
amount: Salario amount: Salario
basicSalary: Salario transportistas basicSalary: Salario transportistas
notes: Notas notes: Notas

View File

@ -25,12 +25,13 @@ const $props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const { hasAccount } = toRefs($props); const { hasAccount } = toRefs($props);
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const arrayData = useArrayData('Account');
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const { notify } = useQuasar(); const { notify } = useQuasar();
const account = computed(() => useArrayData('Account').store.data[0]); const account = computed(() => arrayData.store.data);
account.value.hasAccount = hasAccount.value; account.value.hasAccount = hasAccount.value;
const entityId = computed(() => +route.params.id); const entityId = computed(() => +route.params.id);
const hasitManagementAccess = ref(); const hasitManagementAccess = ref();
@ -39,7 +40,7 @@ const isHimself = computed(() => user.value.id === account.value.id);
const url = computed(() => const url = computed(() =>
isHimself.value isHimself.value
? 'Accounts/change-password' ? 'Accounts/change-password'
: `Accounts/${entityId.value}/setPassword` : `Accounts/${entityId.value}/setPassword`,
); );
async function updateStatusAccount(active) { async function updateStatusAccount(active) {
@ -153,6 +154,7 @@ onMounted(() => {
t('account.card.actions.disableAccount.title'), t('account.card.actions.disableAccount.title'),
t('account.card.actions.disableAccount.subtitle'), t('account.card.actions.disableAccount.subtitle'),
() => deleteAccount(), () => deleteAccount(),
() => deleteAccount(),
) )
" "
> >
@ -172,6 +174,7 @@ onMounted(() => {
t('account.card.actions.enableAccount.title'), t('account.card.actions.enableAccount.title'),
t('account.card.actions.enableAccount.subtitle'), t('account.card.actions.enableAccount.subtitle'),
() => updateStatusAccount(true), () => updateStatusAccount(true),
() => updateStatusAccount(true),
) )
" "
> >
@ -186,6 +189,7 @@ onMounted(() => {
t('account.card.actions.disableAccount.title'), t('account.card.actions.disableAccount.title'),
t('account.card.actions.disableAccount.subtitle'), t('account.card.actions.disableAccount.subtitle'),
() => updateStatusAccount(false), () => updateStatusAccount(false),
() => updateStatusAccount(false),
) )
" "
> >
@ -201,6 +205,7 @@ onMounted(() => {
t('account.card.actions.activateUser.title'), t('account.card.actions.activateUser.title'),
t('account.card.actions.activateUser.title'), t('account.card.actions.activateUser.title'),
() => updateStatusUser(true), () => updateStatusUser(true),
() => updateStatusUser(true),
) )
" "
> >
@ -215,6 +220,7 @@ onMounted(() => {
t('account.card.actions.deactivateUser.title'), t('account.card.actions.deactivateUser.title'),
t('account.card.actions.deactivateUser.title'), t('account.card.actions.deactivateUser.title'),
() => updateStatusUser(false), () => updateStatusUser(false),
() => updateStatusUser(false),
) )
" "
> >

View File

@ -143,6 +143,7 @@ const exprBuilder = (param, value) => {
outlined outlined
rounded rounded
auto-load auto-load
sortBy="name ASC"
/></QItemSection> /></QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">

View File

@ -78,10 +78,20 @@ const columns = computed(() => [
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'], fields: ['id', 'name', 'firstName'],
where: { role: 'salesPerson' }, where: { role: 'salesPerson' },
optionFilter: 'firstName', optionFilter: 'firstName',
}, },
columnFilter: {
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name', 'firstName'],
where: { role: 'salesPerson' },
optionLabel: 'firstName',
optionValue: 'id',
},
},
create: false, create: false,
columnField: { columnField: {
component: null, component: null,

View File

@ -77,7 +77,6 @@ onBeforeMount(() => {
function setPaymentType(accounting) { function setPaymentType(accounting) {
if (!accounting) return; if (!accounting) return;
accountingType.value = accounting.accountingType; accountingType.value = accounting.accountingType;
initialData.description = []; initialData.description = [];
initialData.payed = Date.vnNew(); initialData.payed = Date.vnNew();
isCash.value = accountingType.value.code == 'cash'; isCash.value = accountingType.value.code == 'cash';
@ -87,14 +86,14 @@ function setPaymentType(accounting) {
initialData.payed.getDate() + accountingType.value.daysInFuture, initialData.payed.getDate() + accountingType.value.daysInFuture,
); );
maxAmount.value = accountingType.value && accountingType.value.maxAmount; maxAmount.value = accountingType.value && accountingType.value.maxAmount;
if (accountingType.value.code == 'compensation') if (accountingType.value.code == 'compensation')
return (initialData.description = ''); return (initialData.description = '');
if (accountingType.value.receiptDescription)
initialData.description.push(accountingType.value.receiptDescription);
if (initialData.description) initialData.description.push(initialData.description);
initialData.description = initialData.description.join(', '); let descriptions = [];
if (accountingType.value.receiptDescription)
descriptions.push(accountingType.value.receiptDescription);
if (initialData.description) descriptions.push(initialData.description);
initialData.description = descriptions.join(', ');
} }
const calculateFromAmount = (event) => { const calculateFromAmount = (event) => {

View File

@ -54,6 +54,7 @@ const columns = [
toggleIndeterminate: false, toggleIndeterminate: false,
}, },
create: true, create: true,
createOrder: 12,
width: '25px', width: '25px',
}, },
{ {
@ -61,9 +62,10 @@ const columns = [
name: 'workerFk', name: 'workerFk',
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Workers/search', url: 'TicketRequests/getItemTypeWorker',
fields: ['id', 'nickname'], fields: ['id', 'nickname'],
optionLabel: 'nickname', optionLabel: 'nickname',
sortBy: 'nickname ASC',
optionValue: 'id', optionValue: 'id',
}, },
visible: false, visible: false,
@ -87,15 +89,6 @@ const columns = [
isEditable: false, isEditable: false,
columnFilter: false, columnFilter: false,
}, },
{
name: 'entryFk',
isId: true,
visible: false,
isEditable: false,
disable: true,
create: true,
columnFilter: false,
},
{ {
align: 'center', align: 'center',
label: 'Id', label: 'Id',
@ -137,6 +130,7 @@ const columns = [
name: 'itemFk', name: 'itemFk',
visible: false, visible: false,
create: true, create: true,
createOrder: 0,
columnFilter: false, columnFilter: false,
}, },
{ {
@ -160,6 +154,8 @@ const columns = [
name: 'stickers', name: 'stickers',
component: 'input', component: 'input',
create: true, create: true,
createOrder: 1,
attrs: { attrs: {
positive: false, positive: false,
}, },
@ -271,6 +267,7 @@ const columns = [
}, },
width: '45px', width: '45px',
create: true, create: true,
createOrder: 3,
style: getQuantityStyle, style: getQuantityStyle,
}, },
{ {
@ -280,6 +277,7 @@ const columns = [
toolTip: t('Buying value'), toolTip: t('Buying value'),
name: 'buyingValue', name: 'buyingValue',
create: true, create: true,
createOrder: 2,
component: 'number', component: 'number',
attrs: { attrs: {
positive: false, positive: false,
@ -312,6 +310,7 @@ const columns = [
toolTip: t('Package'), toolTip: t('Package'),
name: 'price2', name: 'price2',
component: 'number', component: 'number',
createDisable: true,
width: '35px', width: '35px',
create: true, create: true,
format: (row) => parseFloat(row['price2']).toFixed(2), format: (row) => parseFloat(row['price2']).toFixed(2),
@ -321,6 +320,7 @@ const columns = [
label: t('Box'), label: t('Box'),
name: 'price3', name: 'price3',
component: 'number', component: 'number',
createDisable: true,
cellEvent: { cellEvent: {
'update:modelValue': async (value, oldValue, row) => { 'update:modelValue': async (value, oldValue, row) => {
row['price2'] = row['price2'] * (value / oldValue); row['price2'] = row['price2'] * (value / oldValue);
@ -508,13 +508,14 @@ async function setBuyUltimate(itemFk, data) {
}, },
}); });
const buyUltimateData = buyUltimate.data[0]; const buyUltimateData = buyUltimate.data[0];
if (!buyUltimateData) return;
const allowedKeys = columns const allowedKeys = columns
.filter((col) => col.create === true) .filter((col) => col.create === true)
.map((col) => col.name); .map((col) => col.name);
allowedKeys.forEach((key) => { allowedKeys.forEach((key) => {
if (buyUltimateData.hasOwnProperty(key) && key !== 'entryFk') { if (buyUltimateData?.hasOwnProperty(key) && key !== 'entryFk') {
if (!['stickers', 'quantity'].includes(key)) data[key] = buyUltimateData[key]; if (!['stickers', 'quantity'].includes(key)) data[key] = buyUltimateData[key];
} }
}); });
@ -607,6 +608,7 @@ onMounted(() => {
ref="entryBuysRef" ref="entryBuysRef"
data-key="EntryBuys" data-key="EntryBuys"
:url="`Entries/${entityId}/getBuyList`" :url="`Entries/${entityId}/getBuyList`"
search-url="EntryBuys"
save-url="Buys/crud" save-url="Buys/crud"
:disable-option="{ card: true }" :disable-option="{ card: true }"
v-model:selected="selectedRows" v-model:selected="selectedRows"
@ -636,21 +638,24 @@ onMounted(() => {
isFullWidth: true, isFullWidth: true,
containerStyle: { containerStyle: {
display: 'flex', display: 'flex',
'flex-wrap': 'wrap',
gap: '16px', gap: '16px',
position: 'relative', position: 'relative',
height: '450px',
}, },
columnGridStyle: { columnGridStyle: {
'max-width': '50%', 'max-width': '50%',
flex: 1,
'margin-right': '30px', 'margin-right': '30px',
flex: 1,
}, },
previousStyle: {
'max-width': '30%',
height: '500px',
},
displayPrevious: true,
}" }"
:is-editable="editableMode" :is-editable="editableMode"
:without-header="!editableMode" :without-header="!editableMode"
:with-filters="editableMode" :with-filters="editableMode"
:right-search="true" :right-search="editableMode"
:right-search-icon="true" :right-search-icon="true"
:row-click="false" :row-click="false"
:columns="columns" :columns="columns"
@ -660,6 +665,7 @@ onMounted(() => {
auto-load auto-load
footer footer
data-cy="entry-buys" data-cy="entry-buys"
overlay
> >
<template #column-hex="{ row }"> <template #column-hex="{ row }">
<VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" />

View File

@ -248,7 +248,7 @@ const entryFilterPanel = ref();
<i18n> <i18n>
en: en:
params: params:
isExcludedFromAvailable: Inventory isExcludedFromAvailable: Is excluded
isOrdered: Ordered isOrdered: Ordered
isReceived: Received isReceived: Received
isConfirmed: Confirmed isConfirmed: Confirmed

View File

@ -11,6 +11,8 @@ import VnTable from 'components/VnTable/VnTable.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import EntrySummary from './Card/EntrySummary.vue';
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
@ -18,6 +20,7 @@ const defaultEntry = ref({});
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const dataKey = 'EntryList'; const dataKey = 'EntryList';
const { viewSummary } = useSummaryDialog();
const entryQueryFilter = { const entryQueryFilter = {
include: [ include: [
@ -222,6 +225,19 @@ const columns = computed(() => [
visible: false, visible: false,
create: true, create: true,
}, },
{
align: 'right',
label: '',
name: 'tableActions',
actions: [
{
title: t('components.smartCard.viewSummary'),
icon: 'preview',
isPrimary: true,
action: (row) => viewSummary(row.id, EntrySummary, 'xlg-width'),
},
],
},
]); ]);
function getBadgeAttrs(row) { function getBadgeAttrs(row) {
const date = row.landed; const date = row.landed;
@ -267,16 +283,7 @@ onBeforeMount(async () => {
</script> </script>
<template> <template>
<VnSection <VnSection :data-key="dataKey" prefix="entry">
:data-key="dataKey"
prefix="entry"
url="Entries/filter"
:array-data-props="{
url: 'Entries/filter',
order: 'landed DESC',
userFilter: EntryFilter,
}"
>
<template #advanced-menu> <template #advanced-menu>
<EntryFilter :data-key="dataKey" /> <EntryFilter :data-key="dataKey" />
</template> </template>
@ -285,6 +292,7 @@ onBeforeMount(async () => {
v-if="defaultEntry.defaultSupplierFk" v-if="defaultEntry.defaultSupplierFk"
ref="tableRef" ref="tableRef"
:data-key="dataKey" :data-key="dataKey"
search-url="EntryList"
url="Entries/filter" url="Entries/filter"
:filter="entryQueryFilter" :filter="entryQueryFilter"
order="landed DESC" order="landed DESC"

View File

@ -19,6 +19,7 @@ const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const footer = ref({ bought: 0, reserve: 0 });
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -38,16 +39,14 @@ const columns = computed(() => [
cardVisible: true, cardVisible: true,
create: true, create: true,
attrs: { attrs: {
url: 'Workers/activeWithInheritedRole', url: 'TicketRequests/getItemTypeWorker',
fields: ['id', 'name', 'nickname'], fields: ['id', 'nickname'],
where: { role: 'buyer' },
optionFilter: 'firstName',
optionLabel: 'nickname', optionLabel: 'nickname',
sortBy: 'nickname ASC',
optionValue: 'id', optionValue: 'id',
useLike: false,
}, },
columnFilter: false, columnFilter: false,
width: '70px', width: '50px',
}, },
{ {
align: 'center', align: 'center',
@ -58,6 +57,7 @@ const columns = computed(() => [
component: 'number', component: 'number',
summation: true, summation: true,
width: '50px', width: '50px',
format: ({ reserve }, dashIfEmpty) => dashIfEmpty(round(reserve)),
}, },
{ {
align: 'center', align: 'center',
@ -65,6 +65,7 @@ const columns = computed(() => [
name: 'bought', name: 'bought',
summation: true, summation: true,
cardVisible: true, cardVisible: true,
style: ({ reserve, bought }) => boughtStyle(bought, reserve),
columnFilter: false, columnFilter: false,
}, },
{ {
@ -95,7 +96,6 @@ const columns = computed(() => [
}, },
}, },
], ],
'data-cy': 'table-actions',
}, },
]); ]);
@ -137,20 +137,20 @@ function openDialog() {
} }
function setFooter(data) { function setFooter(data) {
const footer = { footer.value = { bought: 0, reserve: 0 };
bought: 0,
reserve: 0,
};
data.forEach((row) => { data.forEach((row) => {
footer.bought += row?.bought; footer.value.bought += row?.bought;
footer.reserve += row?.reserve; footer.value.reserve += row?.reserve;
}); });
tableRef.value.footer = footer;
} }
function round(value) { function round(value) {
return Math.round(value * 100) / 100; return Math.round(value * 100) / 100;
} }
function boughtStyle(bought, reserve) {
return reserve < bought ? { color: 'var(--q-negative)' } : '';
}
</script> </script>
<template> <template>
<VnSubToolbar> <VnSubToolbar>
@ -253,24 +253,14 @@ function round(value) {
<WorkerDescriptorProxy :id="row?.workerFk" /> <WorkerDescriptorProxy :id="row?.workerFk" />
</span> </span>
</template> </template>
<template #column-bought="{ row }">
<span :class="{ 'text-negative': row.reserve < row.bought }">
{{ row?.bought }}
</span>
</template>
<template #column-footer-reserve> <template #column-footer-reserve>
<span> <span>
{{ round(tableRef.footer.reserve) }} {{ round(footer.reserve) }}
</span> </span>
</template> </template>
<template #column-footer-bought> <template #column-footer-bought>
<span <span :style="boughtStyle(footer?.bought, footer?.reserve)">
:class="{ {{ round(footer.bought) }}
'text-negative':
tableRef.footer.reserve < tableRef.footer.bought,
}"
>
{{ round(tableRef.footer.bought) }}
</span> </span>
</template> </template>
</VnTable> </VnTable>
@ -286,7 +276,7 @@ function round(value) {
justify-content: center; justify-content: center;
} }
.column { .column {
min-width: 40%; min-width: 35%;
margin-top: 5%; margin-top: 5%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -14,7 +14,7 @@ const $props = defineProps({
required: true, required: true,
}, },
dated: { dated: {
type: Date, type: [Date, String],
required: true, required: true,
}, },
}); });

View File

@ -202,7 +202,7 @@ function setCursor(ref) {
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
@keydown.tab=" @keydown.tab.prevent="
autocompleteExpense( autocompleteExpense(
$event, $event,
row, row,

View File

@ -97,16 +97,19 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'isActive', name: 'isActive',
label: t('invoiceOut.negativeBases.active'), label: t('invoiceOut.negativeBases.active'),
component: 'checkbox',
}, },
{ {
align: 'left', align: 'left',
name: 'hasToInvoice', name: 'hasToInvoice',
label: t('invoiceOut.negativeBases.hasToInvoice'), label: t('invoiceOut.negativeBases.hasToInvoice'),
component: 'checkbox',
}, },
{ {
align: 'left', align: 'left',
name: 'hasVerifiedData', name: 'isTaxDataChecked',
label: t('invoiceOut.negativeBases.verifiedData'), label: t('invoiceOut.negativeBases.verifiedData'),
component: 'checkbox',
}, },
{ {
align: 'left', align: 'left',
@ -142,7 +145,7 @@ const downloadCSV = async () => {
await invoiceOutGlobalStore.getNegativeBasesCsv( await invoiceOutGlobalStore.getNegativeBasesCsv(
userParams.from, userParams.from,
userParams.to, userParams.to,
filterParams filterParams,
); );
}; };
</script> </script>

View File

@ -120,22 +120,9 @@ const updateStock = async () => {
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('globals.producer')" :value="dashIfEmpty(entity.subName)" /> <VnLv :label="t('globals.producer')" :value="dashIfEmpty(entity.subName)" />
<VnLv <VnLv v-if="entity?.value5" :label="entity?.tag5" :value="entity.value5" />
v-if="entity.value5" <VnLv v-if="entity?.value6" :label="entity?.tag6" :value="entity.value6" />
:label="t('item.descriptor.color')" <VnLv v-if="entity?.value7" :label="entity?.tag7" :value="entity.value7" />
:value="entity.value5"
>
</VnLv>
<VnLv
v-if="entity.value6"
:label="t('item.descriptor.category')"
:value="entity.value6"
/>
<VnLv
v-if="entity.value7"
:label="t('item.list.stems')"
:value="entity.value7"
/>
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
<QCardActions v-if="entity" class="q-gutter-x-md"> <QCardActions v-if="entity" class="q-gutter-x-md">

View File

@ -12,7 +12,7 @@ import FetchData from 'components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import { toDateFormat } from 'src/filters/date.js'; import { toDateTimeFormat } from 'src/filters/date.js';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { date } from 'quasar'; import { date } from 'quasar';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
@ -27,7 +27,7 @@ const user = state.getUser();
const today = Date.vnNew(); const today = Date.vnNew();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
const warehousesOptions = ref([]); const warehousesOptions = ref([]);
const itemBalances = computed(() => arrayDataItemBalances.store.data); const itemBalances = computed(() => arrayDataItemBalances.store.data || []);
const where = computed(() => arrayDataItemBalances.store.filter.where || {}); const where = computed(() => arrayDataItemBalances.store.filter.where || {});
const showWhatsBeforeInventory = ref(false); const showWhatsBeforeInventory = ref(false);
const inventoriedDate = ref(null); const inventoriedDate = ref(null);
@ -143,7 +143,12 @@ onMounted(async () => {
const fetchItemBalances = async () => await arrayDataItemBalances.fetch({}); const fetchItemBalances = async () => await arrayDataItemBalances.fetch({});
const getBadgeAttrs = (_date) => { const getBadgeAttrs = (_date) => {
const isSameDate = date.isSameDate(today, _date); let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(_date);
timeTicket.setHours(0, 0, 0, 0);
const isSameDate = date.isSameDate(today, timeTicket);
const attrs = { const attrs = {
'text-color': isSameDate ? 'black' : 'white', 'text-color': isSameDate ? 'black' : 'white',
color: isSameDate ? 'warning' : 'transparent', color: isSameDate ? 'warning' : 'transparent',
@ -244,7 +249,7 @@ async function updateWarehouse(warehouseFk) {
dense dense
style="font-size: 14px" style="font-size: 14px"
> >
{{ toDateFormat(row.shipped) }} {{ toDateTimeFormat(row.shipped) }}
</QBadge> </QBadge>
</QTd> </QTd>
</template> </template>
@ -313,8 +318,8 @@ async function updateWarehouse(warehouseFk) {
row.lineFk == row.lastPreparedLineFk row.lineFk == row.lastPreparedLineFk
? 'black' ? 'black'
: row.balance < 0 : row.balance < 0
? 'negative' ? 'negative'
: '' : ''
" "
dense dense
style="font-size: 14px" style="font-size: 14px"

View File

@ -11,7 +11,6 @@ import { toCurrency } from 'filters/index';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const from = ref(); const from = ref();
@ -41,7 +40,7 @@ const itemLastEntries = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
label: 'Nv', label: 'NV',
name: 'ig', name: 'ig',
align: 'center', align: 'center',
}, },
@ -70,6 +69,7 @@ const columns = computed(() => [
field: 'reference', field: 'reference',
align: 'center', align: 'center',
format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3), format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3),
style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.printedStickers'), label: t('lastEntries.printedStickers'),
@ -84,6 +84,7 @@ const columns = computed(() => [
field: 'stickers', field: 'stickers',
align: 'center', align: 'center',
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
style: (row) => highlightedRow(row),
}, },
{ {
label: 'Packing', label: 'Packing',
@ -102,12 +103,14 @@ const columns = computed(() => [
name: 'stems', name: 'stems',
field: 'stems', field: 'stems',
align: 'center', align: 'center',
style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.quantity'), label: t('lastEntries.quantity'),
name: 'quantity', name: 'quantity',
field: 'quantity', field: 'quantity',
align: 'center', align: 'center',
style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.cost'), label: t('lastEntries.cost'),
@ -120,12 +123,14 @@ const columns = computed(() => [
name: 'weight', name: 'weight',
field: 'weight', field: 'weight',
align: 'center', align: 'center',
style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.cube'), label: t('lastEntries.cube'),
name: 'cube', name: 'cube',
field: 'packagingFk', field: 'packagingFk',
align: 'center', align: 'center',
style: (row) => highlightedRow(row),
}, },
{ {
label: t('lastEntries.supplier'), label: t('lastEntries.supplier'),
@ -208,6 +213,14 @@ onMounted(async () => {
function getBadgeClass(groupingMode, expectedGrouping) { function getBadgeClass(groupingMode, expectedGrouping) {
return groupingMode === expectedGrouping ? 'accent-badge' : 'simple-badge'; return groupingMode === expectedGrouping ? 'accent-badge' : 'simple-badge';
} }
function highlightedRow(row) {
return row?.isInventorySupplier
? {
'background-color': 'var(--vn-section-hover-color)',
}
: '';
}
</script> </script>
<template> <template>
<VnSubToolbar> <VnSubToolbar>
@ -236,7 +249,7 @@ function getBadgeClass(groupingMode, expectedGrouping) {
:no-data-label="t('globals.noResults')" :no-data-label="t('globals.noResults')"
> >
<template #body-cell-ig="{ row }"> <template #body-cell-ig="{ row }">
<QTd class="text-center"> <QTd class="text-center" :style="highlightedRow(row)">
<QIcon <QIcon
:name="row.isIgnored ? 'check_box' : 'check_box_outline_blank'" :name="row.isIgnored ? 'check_box' : 'check_box_outline_blank'"
style="color: var(--vn-label-color)" style="color: var(--vn-label-color)"
@ -245,38 +258,38 @@ function getBadgeClass(groupingMode, expectedGrouping) {
</QTd> </QTd>
</template> </template>
<template #body-cell-warehouse="{ row }"> <template #body-cell-warehouse="{ row }">
<QTd> <QTd :style="highlightedRow(row)">
<span>{{ row.warehouse }}</span> <span>{{ row.warehouse }}</span>
</QTd> </QTd>
</template> </template>
<template #body-cell-date="{ row }"> <template #body-cell-date="{ row }">
<QTd class="text-center"> <QTd class="text-center" :style="highlightedRow(row)">
<VnDateBadge :date="row.landed" /> <VnDateBadge :date="row.landed" />
</QTd> </QTd>
</template> </template>
<template #body-cell-entry="{ row }"> <template #body-cell-entry="{ row }">
<QTd @click.stop> <QTd @click.stop :style="highlightedRow(row)">
<div class="full-width flex justify-center"> <div class="full-width flex justify-center">
<EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense /> <EntryDescriptorProxy :id="row.entryFk" class="q-ma-none" dense />
<span class="link">{{ row.entryFk }}</span> <span class="link">{{ row.entryFk }}</span>
</div> </div>
</QTd> </QTd>
</template> </template>
<template #body-cell-pvp="{ value }"> <template #body-cell-pvp="{ row, value }">
<QTd @click.stop class="text-center"> <QTd @click.stop class="text-center" :style="highlightedRow(row)">
<span> {{ value }}</span> <span> {{ value }}</span>
<QTooltip> {{ t('lastEntries.grouping') }}/Packing </QTooltip></QTd <QTooltip> {{ t('lastEntries.grouping') }}/Packing </QTooltip>
> </QTd>
</template> </template>
<template #body-cell-printedStickers="{ row }"> <template #body-cell-printedStickers="{ row }">
<QTd @click.stop class="text-center"> <QTd @click.stop class="text-center" :style="highlightedRow(row)">
<span style="color: var(--vn-label-color)"> <span style="color: var(--vn-label-color)">
{{ row.printedStickers }}</span {{ row.printedStickers }}</span
> >
</QTd> </QTd>
</template> </template>
<template #body-cell-packing="{ row }"> <template #body-cell-packing="{ row }">
<QTd @click.stop> <QTd @click.stop :style="highlightedRow(row)">
<QBadge <QBadge
class="center-content" class="center-content"
:class="getBadgeClass(row.groupingMode, 'packing')" :class="getBadgeClass(row.groupingMode, 'packing')"
@ -288,7 +301,7 @@ function getBadgeClass(groupingMode, expectedGrouping) {
</QTd> </QTd>
</template> </template>
<template #body-cell-grouping="{ row }"> <template #body-cell-grouping="{ row }">
<QTd @click.stop> <QTd @click.stop :style="highlightedRow(row)">
<QBadge <QBadge
class="center-content" class="center-content"
:class="getBadgeClass(row.groupingMode, 'grouping')" :class="getBadgeClass(row.groupingMode, 'grouping')"
@ -300,7 +313,7 @@ function getBadgeClass(groupingMode, expectedGrouping) {
</QTd> </QTd>
</template> </template>
<template #body-cell-cost="{ row }"> <template #body-cell-cost="{ row }">
<QTd @click.stop class="text-center"> <QTd @click.stop class="text-center" :style="highlightedRow(row)">
<span> <span>
{{ toCurrency(row.cost, 'EUR', 3) }} {{ toCurrency(row.cost, 'EUR', 3) }}
<QTooltip> <QTooltip>
@ -319,7 +332,7 @@ function getBadgeClass(groupingMode, expectedGrouping) {
</QTd> </QTd>
</template> </template>
<template #body-cell-supplier="{ row }"> <template #body-cell-supplier="{ row }">
<QTd @click.stop> <QTd @click.stop :style="highlightedRow(row)">
<div class="full-width flex justify-left"> <div class="full-width flex justify-left">
<QBadge <QBadge
:class=" :class="
@ -354,7 +367,6 @@ function getBadgeClass(groupingMode, expectedGrouping) {
.th :first-child { .th :first-child {
.td { .td {
text-align: center; text-align: center;
background-color: red;
} }
} }
.accent-badge { .accent-badge {

View File

@ -87,7 +87,7 @@ const insertTag = (rows) => {
tagFk: undefined, tagFk: undefined,
}" }"
:default-remove="false" :default-remove="false"
:filter="{ :user-filter="{
fields: ['id', 'itemFk', 'tagFk', 'value', 'priority'], fields: ['id', 'itemFk', 'tagFk', 'value', 'priority'],
where: { itemFk: route.params.id }, where: { itemFk: route.params.id },
include: { include: {
@ -119,6 +119,7 @@ const insertTag = (rows) => {
" "
:required="true" :required="true"
:rules="validate('itemTag.tagFk')" :rules="validate('itemTag.tagFk')"
:data-cy="`tag${row?.tag?.name}`"
/> />
<VnSelect <VnSelect
v-if="row.tag?.isFree === false" v-if="row.tag?.isFree === false"
@ -145,6 +146,7 @@ const insertTag = (rows) => {
:label="t('itemTags.value')" :label="t('itemTags.value')"
:is-clearable="false" :is-clearable="false"
@keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)" @keyup.enter.stop="(data) => itemTagsRef.onSubmit(data)"
:data-cy="`tag${row?.tag?.name}Value`"
/> />
<VnInput <VnInput
:label="t('itemBasicData.relevancy')" :label="t('itemBasicData.relevancy')"
@ -162,6 +164,7 @@ const insertTag = (rows) => {
name="delete" name="delete"
size="sm" size="sm"
dense dense
:data-cy="`deleteTag${row?.tag?.name}`"
> >
<QTooltip> <QTooltip>
{{ t('itemTags.removeTag') }} {{ t('itemTags.removeTag') }}
@ -177,6 +180,7 @@ const insertTag = (rows) => {
icon="add" icon="add"
v-shortcut="'+'" v-shortcut="'+'"
fab fab
data-cy="createNewTag"
> >
<QTooltip> <QTooltip>
{{ t('itemTags.addTag') }} {{ t('itemTags.addTag') }}

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { dashIfEmpty, toDate, toHour } from 'src/filters'; import { toDate, toHour } from 'src/filters';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { usePrintService } from 'src/composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref, markRaw } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { toHour } from 'src/filters'; import { toHour } from 'src/filters';
@ -8,6 +8,7 @@ import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
@ -38,17 +39,7 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'workerFk', name: 'workerFk',
label: t('route.Worker'), label: t('route.Worker'),
component: 'select', component: markRaw(VnSelectWorker),
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
useLike: false,
optionFilter: 'firstName',
find: {
value: 'workerFk',
label: 'workerUserName',
},
},
create: true, create: true,
cardVisible: true, cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
@ -59,6 +50,10 @@ const columns = computed(() => [
name: 'agencyName', name: 'agencyName',
label: t('route.Agency'), label: t('route.Agency'),
cardVisible: true, cardVisible: true,
},
{
label: t('route.Agency'),
name: 'agencyModeFk',
component: 'select', component: 'select',
attrs: { attrs: {
url: 'agencyModes', url: 'agencyModes',
@ -69,14 +64,19 @@ const columns = computed(() => [
}, },
}, },
create: true, create: true,
columnClass: 'expand',
columnFilter: false, columnFilter: false,
visible: false,
}, },
{ {
align: 'left', align: 'left',
name: 'vehiclePlateNumber', name: 'vehiclePlateNumber',
label: t('route.Vehicle'), label: t('route.Vehicle'),
cardVisible: true, cardVisible: true,
},
{
name: 'vehicleFk',
label: t('route.Vehicle'),
cardVisible: true,
component: 'select', component: 'select',
attrs: { attrs: {
url: 'vehicles', url: 'vehicles',
@ -90,6 +90,7 @@ const columns = computed(() => [
}, },
create: true, create: true,
columnFilter: false, columnFilter: false,
visible: false,
}, },
{ {
align: 'left', align: 'left',
@ -156,6 +157,7 @@ const columns = computed(() => [
<VnTable <VnTable
:data-key :data-key
:columns="columns" :columns="columns"
ref="tableRef"
:right-search="false" :right-search="false"
redirect="route" redirect="route"
:create="{ :create="{

View File

@ -6,7 +6,10 @@ import VnSection from 'src/components/common/VnSection.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 FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import SupplierSummary from './Card/SupplierSummary.vue';
const { viewSummary } = useSummaryDialog();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const dataKey = 'SupplierList'; const dataKey = 'SupplierList';
@ -103,6 +106,19 @@ const columns = computed(() => [
}, },
}, },
}, },
{
align: 'right',
label: '',
name: 'tableActions',
actions: [
{
title: t('components.smartCard.viewSummary'),
icon: 'preview',
isPrimary: true,
action: (row) => viewSummary(row.id, SupplierSummary, 'md-width'),
},
],
},
]); ]);
</script> </script>
<template> <template>

View File

@ -1,32 +1,26 @@
<script setup> <script setup>
import { ref } from 'vue'; import axios from 'axios';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import VnUsesMana from 'components/ui/VnUsesMana.vue'; import VnUsesMana from 'components/ui/VnUsesMana.vue';
const $props = defineProps({ const $props = defineProps({
mana: {
type: Number,
default: null,
},
newPrice: { newPrice: {
type: Number, type: Number,
default: 0, default: 0,
}, },
usesMana: {
type: Boolean,
default: false,
},
manaCode: {
type: String,
default: 'mana',
},
sale: { sale: {
type: Object, type: Object,
default: null, default: null,
}, },
}); });
const route = useRoute();
const mana = ref(null);
const usesMana = ref(false);
const emit = defineEmits(['save', 'cancel']); const emit = defineEmits(['save', 'cancel']);
const { t } = useI18n(); const { t } = useI18n();
@ -38,32 +32,47 @@ const save = (sale = $props.sale) => {
QPopupProxyRef.value.hide(); QPopupProxyRef.value.hide();
}; };
const getMana = async () => {
const { data } = await axios.get(`Tickets/${route.params.id}/getSalesPersonMana`);
mana.value = data;
await getUsesMana();
};
const getUsesMana = async () => {
const { data } = await axios.get('Sales/usesMana');
usesMana.value = data;
};
const cancel = () => { const cancel = () => {
emit('cancel'); emit('cancel');
QPopupProxyRef.value.hide(); QPopupProxyRef.value.hide();
}; };
const hasMana = computed(() => typeof mana.value === 'number');
defineExpose({ save }); defineExpose({ save });
</script> </script>
<template> <template>
<QPopupProxy ref="QPopupProxyRef" data-cy="ticketEditManaProxy"> <QPopupProxy
ref="QPopupProxyRef"
@before-show="getMana"
data-cy="ticketEditManaProxy"
>
<div class="container"> <div class="container">
<QSpinner v-if="!mana" color="primary" size="md" /> <div class="header">Mana: {{ toCurrency(mana) }}</div>
<div v-else> <QSpinner v-if="!hasMana" color="primary" size="md" />
<div class="header">Mana: {{ toCurrency(mana) }}</div> <div class="q-pa-md" v-else>
<div class="q-pa-md"> <slot :popup="QPopupProxyRef" />
<slot :popup="QPopupProxyRef" /> <div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm">
<div v-if="usesMana" class="column q-gutter-y-sm q-mt-sm"> <VnUsesMana :mana-code="manaCode" />
<VnUsesMana :mana-code="manaCode" /> </div>
</div> <div v-if="newPrice" class="column items-center q-mt-lg">
<div v-if="newPrice" class="column items-center q-mt-lg"> <span class="text-primary">{{ t('New price') }}</span>
<span class="text-primary">{{ t('New price') }}</span> <span class="text-subtitle1">
<span class="text-subtitle1"> {{ toCurrency($props.newPrice) }}
{{ toCurrency($props.newPrice) }} </span>
</span>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<QBtn <QBtn
color="primary" color="primary"

View File

@ -45,7 +45,6 @@ const isTicketEditable = ref(false);
const sales = ref([]); const sales = ref([]);
const editableStatesOptions = ref([]); const editableStatesOptions = ref([]);
const selectedSales = ref([]); const selectedSales = ref([]);
const mana = ref(null);
const manaCode = ref('mana'); const manaCode = ref('mana');
const ticketState = computed(() => store.data?.ticketState?.state?.code); const ticketState = computed(() => store.data?.ticketState?.state?.code);
const transfer = ref({ const transfer = ref({
@ -175,17 +174,21 @@ const getSaleTotal = (sale) => {
return price - discount; return price - discount;
}; };
const getRowUpdateInputEvents = (sale) => ({
'keyup.enter': () => {
changeQuantity(sale);
},
blur: () => {
changeQuantity(sale);
},
});
const resetChanges = async () => { const resetChanges = async () => {
arrayData.fetch({ append: false }); arrayData.fetch({ append: false });
tableRef.value.reload(); tableRef.value.reload();
}; };
const rowToUpdate = ref(null);
const changeQuantity = async (sale) => { const changeQuantity = async (sale) => {
if ( if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity)
!sale.itemFk ||
sale.quantity == null ||
edit.value?.oldQuantity === sale.quantity
)
return; return;
if (!sale.id) return addSale(sale); if (!sale.id) return addSale(sale);
@ -197,13 +200,10 @@ const changeQuantity = async (sale) => {
const updateQuantity = async (sale) => { const updateQuantity = async (sale) => {
try { try {
let { quantity, id } = sale; let { quantity, id } = sale;
if (!rowToUpdate.value) return;
rowToUpdate.value = null;
sale.isNew = false; sale.isNew = false;
const params = { quantity: quantity }; await axios.post(`Sales/${id}/updateQuantity`, { quantity });
await axios.post(`Sales/${id}/updateQuantity`, params);
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
tableRef.value.reload(); resetChanges();
} catch (e) { } catch (e) {
const { quantity } = tableRef.value.CrudModelRef.originalData.find( const { quantity } = tableRef.value.CrudModelRef.originalData.find(
(s) => s.id === sale.id, (s) => s.id === sale.id,
@ -247,7 +247,7 @@ const updateConcept = async (sale) => {
const data = { newConcept: sale.concept }; const data = { newConcept: sale.concept };
await axios.post(`Sales/${sale.id}/updateConcept`, data); await axios.post(`Sales/${sale.id}/updateConcept`, data);
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
tableRef.value.reload(); resetChanges();
}; };
const DEFAULT_EDIT = { const DEFAULT_EDIT = {
@ -258,18 +258,6 @@ const DEFAULT_EDIT = {
oldQuantity: null, oldQuantity: null,
}; };
const edit = ref({ ...DEFAULT_EDIT }); const edit = ref({ ...DEFAULT_EDIT });
const usesMana = ref(null);
const getUsesMana = async () => {
const { data } = await axios.get('Sales/usesMana');
usesMana.value = data;
};
const getMana = async () => {
const { data } = await axios.get(`Tickets/${route.params.id}/getSalesPersonMana`);
mana.value = data;
await getUsesMana();
};
const selectedValidSales = computed(() => { const selectedValidSales = computed(() => {
if (!sales.value) return; if (!sales.value) return;
@ -277,7 +265,6 @@ const selectedValidSales = computed(() => {
}); });
const onOpenEditPricePopover = async (sale) => { const onOpenEditPricePopover = async (sale) => {
await getMana();
edit.value = { edit.value = {
sale: JSON.parse(JSON.stringify(sale)), sale: JSON.parse(JSON.stringify(sale)),
price: sale.price, price: sale.price,
@ -285,7 +272,6 @@ const onOpenEditPricePopover = async (sale) => {
}; };
const onOpenEditDiscountPopover = async (sale) => { const onOpenEditDiscountPopover = async (sale) => {
await getMana();
if (isLocked.value) return; if (isLocked.value) return;
if (sale) { if (sale) {
edit.value = { edit.value = {
@ -306,14 +292,13 @@ const changePrice = async (sale) => {
await confirmUpdate(() => updatePrice(sale, newPrice)); await confirmUpdate(() => updatePrice(sale, newPrice));
} else updatePrice(sale, newPrice); } else updatePrice(sale, newPrice);
} }
await getMana();
}; };
const updatePrice = async (sale, newPrice) => { const updatePrice = async (sale, newPrice) => {
await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice });
sale.price = newPrice; sale.price = newPrice;
edit.value = { ...DEFAULT_EDIT }; edit.value = { ...DEFAULT_EDIT };
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
tableRef.value.reload(); resetChanges();
}; };
const changeDiscount = async (sale) => { const changeDiscount = async (sale) => {
@ -345,7 +330,7 @@ const updateDiscount = async (sales, newDiscount = null) => {
}; };
await axios.post(`Tickets/${route.params.id}/updateDiscount`, params); await axios.post(`Tickets/${route.params.id}/updateDiscount`, params);
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
tableRef.value.reload(); resetChanges();
}; };
const getNewPrice = computed(() => { const getNewPrice = computed(() => {
@ -413,7 +398,7 @@ const removeSales = async () => {
await axios.post('Sales/deleteSales', params); await axios.post('Sales/deleteSales', params);
removeSelectedSales(); removeSelectedSales();
notify('globals.dataSaved', 'positive'); notify('globals.dataSaved', 'positive');
window.location.reload(); resetChanges();
}; };
const setTransferParams = async () => { const setTransferParams = async () => {
@ -599,9 +584,7 @@ watch(
:is-ticket-editable="isTicketEditable" :is-ticket-editable="isTicketEditable"
:sales="selectedValidSales" :sales="selectedValidSales"
:disable="!hasSelectedRows" :disable="!hasSelectedRows"
:mana="mana"
:ticket-config="ticketConfig" :ticket-config="ticketConfig"
@get-mana="getMana()"
@update-discounts="updateDiscounts" @update-discounts="updateDiscounts"
@refresh-table="resetChanges" @refresh-table="resetChanges"
/> />
@ -772,9 +755,7 @@ watch(
v-if="row.isNew || isTicketEditable" v-if="row.isNew || isTicketEditable"
type="number" type="number"
v-model.number="row.quantity" v-model.number="row.quantity"
@blur="changeQuantity(row)" v-on="getRowUpdateInputEvents(row)"
@keyup.enter.stop="changeQuantity(row)"
@update:model-value="() => (rowToUpdate = row)"
@focus="edit.oldQuantity = row.quantity" @focus="edit.oldQuantity = row.quantity"
/> />
<span v-else>{{ row.quantity }}</span> <span v-else>{{ row.quantity }}</span>
@ -786,7 +767,6 @@ watch(
</QBtn> </QBtn>
<TicketEditManaProxy <TicketEditManaProxy
ref="editPriceProxyRef" ref="editPriceProxyRef"
:mana="mana"
:sale="row" :sale="row"
:new-price="getNewPrice" :new-price="getNewPrice"
@save="changePrice" @save="changePrice"
@ -809,10 +789,8 @@ watch(
<TicketEditManaProxy <TicketEditManaProxy
ref="editManaProxyRef" ref="editManaProxyRef"
:mana="mana"
:sale="row" :sale="row"
:new-price="getNewPrice" :new-price="getNewPrice"
:uses-mana="usesMana"
:mana-code="manaCode" :mana-code="manaCode"
@save="changeDiscount" @save="changeDiscount"
> >

View File

@ -34,10 +34,6 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
mana: {
type: Number,
default: null,
},
ticketConfig: { ticketConfig: {
type: Array, type: Array,
default: () => [], default: () => [],
@ -50,6 +46,7 @@ const { dialog } = useQuasar();
const { notify } = useNotify(); const { notify } = useNotify();
const acl = useAcl(); const acl = useAcl();
const btnDropdownRef = ref(null); const btnDropdownRef = ref(null);
const editManaProxyRef = ref(null);
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const newDiscount = ref(null); const newDiscount = ref(null);
@ -131,13 +128,13 @@ const createClaim = () => {
openConfirmationModal( openConfirmationModal(
t('Claim out of time'), t('Claim out of time'),
t('Do you want to continue?'), t('Do you want to continue?'),
onCreateClaimAccepted onCreateClaimAccepted,
); );
else else
openConfirmationModal( openConfirmationModal(
t('Do you want to create a claim?'), t('Do you want to create a claim?'),
false, false,
onCreateClaimAccepted onCreateClaimAccepted,
); );
}; };
@ -216,8 +213,14 @@ const createRefund = async (withWarehouse) => {
<QItemSection> <QItemSection>
<QItemLabel>{{ t('Update discount') }}</QItemLabel> <QItemLabel>{{ t('Update discount') }}</QItemLabel>
</QItemSection> </QItemSection>
<TicketEditManaProxy :mana="props.mana" @save="changeMultipleDiscount()"> <TicketEditManaProxy
ref="editManaProxyRef"
:sale="row"
@save="changeMultipleDiscount"
>
<VnInput <VnInput
autofocus
@keyup.enter.stop="() => editManaProxyRef.save(row)"
v-model.number="newDiscount" v-model.number="newDiscount"
:label="t('ticketSale.discount')" :label="t('ticketSale.discount')"
type="number" type="number"

View File

@ -46,6 +46,15 @@ const descriptorData = useArrayData('Ticket');
onMounted(async () => { onMounted(async () => {
ticketUrl.value = (await getUrl('ticket/')) + entityId.value + '/'; ticketUrl.value = (await getUrl('ticket/')) + entityId.value + '/';
}); });
const formattedAddress = computed(() => {
if (!ticket.value) return '';
const address = ticket.value.address;
const postcode = address.postalCode;
const province = address.province ? `(${address.province.name})` : '';
return `${address.street} - ${postcode} - ${address.city} ${province}`;
});
function isEditable() { function isEditable() {
try { try {
@ -238,7 +247,7 @@ onMounted(async () => {
/> />
<VnLv <VnLv
:label="t('ticket.summary.consigneeStreet')" :label="t('ticket.summary.consigneeStreet')"
:value="entity.address?.street" :value="formattedAddress"
/> />
</QCard> </QCard>
<QCard class="vn-one" v-if="entity.notes.length"> <QCard class="vn-one" v-if="entity.notes.length">

View File

@ -456,6 +456,7 @@ watch(
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
:no-data-label="t('globals.noResults')" :no-data-label="t('globals.noResults')"
:right-search="false" :right-search="false"
:order="['futureTotalWithVat ASC']"
auto-load auto-load
:disable-option="{ card: true }" :disable-option="{ card: true }"
> >

View File

@ -293,6 +293,7 @@ en:
clientFk: Customer clientFk: Customer
orderFk: Order orderFk: Order
from: From from: From
shipped: Shipped
to: To to: To
salesPersonFk: Salesperson salesPersonFk: Salesperson
stateFk: State stateFk: State
@ -320,6 +321,7 @@ es:
clientFk: Cliente clientFk: Cliente
orderFk: Pedido orderFk: Pedido
from: Desde from: Desde
shipped: F. envío
to: Hasta to: Hasta
salesPersonFk: Comercial salesPersonFk: Comercial
stateFk: Estado stateFk: Estado

View File

@ -108,13 +108,11 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'shippedDate', name: 'shipped',
cardVisible: true, cardVisible: true,
label: t('ticketList.shipped'), label: t('ticketList.shipped'),
columnFilter: { columnFilter: {
component: 'date', component: 'date',
alias: 't',
inWhere: true,
}, },
format: ({ shippedDate }) => toDate(shippedDate), format: ({ shippedDate }) => toDate(shippedDate),
}, },
@ -216,6 +214,7 @@ const columns = computed(() => [
{ {
title: t('components.smartCard.viewSummary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
isPrimary: true,
action: (row, evt) => { action: (row, evt) => {
if (evt && evt.ctrlKey) { if (evt && evt.ctrlKey) {
const url = router.resolve({ const url = router.resolve({
@ -253,7 +252,7 @@ const fetchAvailableAgencies = async (formData) => {
const { options, agency } = response; const { options, agency } = response;
if (options) agenciesOptions.value = options; if (options) agenciesOptions.value = options;
if (agency) formData.agencyModeId = agency; if (agency) formData.agencyModeId = agency.agencyModeFk;
}; };
const fetchClient = async (formData) => { const fetchClient = async (formData) => {

View File

@ -17,6 +17,12 @@ const maritalStatus = [
{ code: 'M', name: t('Married') }, { code: 'M', name: t('Married') },
{ code: 'S', name: t('Single') }, { code: 'S', name: t('Single') },
]; ];
async function setAdvancedSummary(data) {
const advanced = (await useAdvancedSummary('Workers', data.id)) ?? {};
Object.assign(form.value.formData, advanced);
await nextTick();
if (form.value) form.value.hasChanges = false;
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -36,13 +42,7 @@ const maritalStatus = [
:url-update="`Workers/${$route.params.id}`" :url-update="`Workers/${$route.params.id}`"
auto-load auto-load
model="Worker" model="Worker"
@on-fetch=" @on-fetch="setAdvancedSummary"
async (data) => {
Object.assign(data, (await useAdvancedSummary('Workers', data.id)) ?? {});
await $nextTick();
if (form) form.hasChanges = false;
}
"
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>

View File

@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { toDate } from 'src/filters'; import { dashIfEmpty, toDate } from 'src/filters';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios'; import axios from 'axios';
@ -15,7 +15,7 @@ const quasar = useQuasar();
async function reactivateWorker() { async function reactivateWorker() {
const hasToReactive = tableRef.value.CrudModelRef.formData.find( const hasToReactive = tableRef.value.CrudModelRef.formData.find(
(data) => !data.ended (data) => !data.ended,
); );
if (hasToReactive) { if (hasToReactive) {
quasar quasar
@ -35,28 +35,44 @@ async function reactivateWorker() {
} }
} }
const columns = computed(() => [ const columns = computed(() => [
{
name: 'id',
label: t('Id'),
align: 'left',
isId: true,
cardVisible: true,
width: '40px',
},
{
name: 'isHourlyLabor',
label: t('worker.business.tableVisibleColumns.hourlyLabor'),
align: 'left',
component: 'checkbox',
cardVisible: true,
width: '60px',
},
{ {
name: 'started', name: 'started',
label: t('worker.business.tableVisibleColumns.started'), label: t('worker.business.tableVisibleColumns.started'),
align: 'left',
format: ({ started }) => toDate(started), format: ({ started }) => toDate(started),
component: 'date', component: 'date',
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '90px',
}, },
{ {
name: 'ended', name: 'ended',
label: t('worker.business.tableVisibleColumns.ended'), label: t('worker.business.tableVisibleColumns.ended'),
align: 'left',
format: ({ ended }) => toDate(ended), format: ({ ended }) => toDate(ended),
component: 'date', component: 'date',
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '90px',
}, },
{ {
label: t('worker.business.tableVisibleColumns.company'), label: t('worker.business.tableVisibleColumns.company'),
align: 'left', toolTip: t('worker.business.tableVisibleColumns.company'),
name: 'companyCodeFk', name: 'companyCodeFk',
component: 'select', component: 'select',
attrs: { attrs: {
@ -65,23 +81,23 @@ const columns = computed(() => [
optionLabel: 'code', optionLabel: 'code',
optionValue: 'code', optionValue: 'code',
}, },
cardVisible: true,
create: true, create: true,
width: '60px',
}, },
{ {
align: 'left',
name: 'reasonEndFk', name: 'reasonEndFk',
component: 'select', component: 'select',
label: t('worker.business.tableVisibleColumns.reasonEnd'), label: t('worker.business.tableVisibleColumns.reasonEnd'),
toolTip: t('worker.business.tableVisibleColumns.reasonEnd'),
attrs: { attrs: {
url: 'BusinessReasonEnds', url: 'BusinessReasonEnds',
fields: ['id', 'reason'], fields: ['id', 'reason'],
optionLabel: 'reason', optionLabel: 'reason',
}, },
cardVisible: true, cardVisible: true,
format: ({ reason }, dashIfEmpty) => dashIfEmpty(reason),
}, },
{ {
align: 'left',
name: 'departmentFk', name: 'departmentFk',
component: 'select', component: 'select',
label: t('worker.business.tableVisibleColumns.department'), label: t('worker.business.tableVisibleColumns.department'),
@ -89,15 +105,19 @@ const columns = computed(() => [
url: 'Departments', url: 'Departments',
fields: ['id', 'name'], fields: ['id', 'name'],
optionLabel: 'name', optionLabel: 'name',
optionValue: 'id',
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '80px',
format: ({ departmentName }, dashIfEmpty) => dashIfEmpty(departmentName),
}, },
{ {
align: 'left', align: 'left',
name: 'workerBusinessProfessionalCategoryFk', name: 'workerBusinessProfessionalCategoryFk',
component: 'select', component: 'select',
label: t('worker.business.tableVisibleColumns.professionalCategory'), label: t('worker.business.tableVisibleColumns.professionalCategory'),
toolTip: t('worker.business.tableVisibleColumns.professionalCategory'),
attrs: { attrs: {
url: 'WorkerBusinessProfessionalCategories', url: 'WorkerBusinessProfessionalCategories',
fields: ['id', 'description', 'code'], fields: ['id', 'description', 'code'],
@ -105,6 +125,9 @@ const columns = computed(() => [
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '100px',
format: ({ professionalDescription }, dashIfEmpty) =>
dashIfEmpty(professionalDescription),
}, },
{ {
align: 'left', align: 'left',
@ -118,6 +141,8 @@ const columns = computed(() => [
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
format: ({ calendarTypeDescription }, dashIfEmpty) =>
dashIfEmpty(calendarTypeDescription),
}, },
{ {
align: 'left', align: 'left',
@ -131,6 +156,8 @@ const columns = computed(() => [
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '100px',
format: ({ workCenterName }, dashIfEmpty) => dashIfEmpty(workCenterName),
}, },
{ {
align: 'left', align: 'left',
@ -144,6 +171,7 @@ const columns = computed(() => [
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
format: ({ payrollDescription }, dashIfEmpty) => dashIfEmpty(payrollDescription),
}, },
{ {
align: 'left', align: 'left',
@ -157,6 +185,7 @@ const columns = computed(() => [
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
format: ({ occupationName }, dashIfEmpty) => dashIfEmpty(occupationName),
}, },
{ {
align: 'left', align: 'left',
@ -165,6 +194,7 @@ const columns = computed(() => [
component: 'input', component: 'input',
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '50px',
}, },
{ {
align: 'left', align: 'left',
@ -177,6 +207,22 @@ const columns = computed(() => [
}, },
cardVisible: true, cardVisible: true,
create: true, create: true,
format: ({ workerBusinessTypeName }, dashIfEmpty) =>
dashIfEmpty(workerBusinessTypeName),
},
{
align: 'left',
name: 'workerBusinessAgreementFk',
label: t('worker.business.tableVisibleColumns.workerBusinessAgreementName'),
component: 'select',
attrs: {
url: 'WorkerBusinessAgreements',
fields: ['id', 'name'],
},
cardVisible: true,
create: true,
format: ({ workerBusinessAgreementName }, dashIfEmpty) =>
dashIfEmpty(workerBusinessAgreementName),
}, },
{ {
align: 'left', align: 'left',
@ -185,6 +231,7 @@ const columns = computed(() => [
component: 'input', component: 'input',
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '70px',
}, },
{ {
align: 'left', align: 'left',
@ -193,6 +240,7 @@ const columns = computed(() => [
component: 'input', component: 'input',
cardVisible: true, cardVisible: true,
create: true, create: true,
width: '70px',
}, },
{ {
name: 'notes', name: 'notes',
@ -208,27 +256,27 @@ const columns = computed(() => [
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="WorkerBusiness" data-key="WorkerBusiness"
:url="`Workers/${entityId}/Business`" :url="`Workers/${entityId}/getWorkerBusiness`"
save-url="/Businesses/crud" save-url="/Businesses/crud"
:create="{ :create="{
urlCreate: `Workers/${entityId}/Business`, urlCreate: `Workers/${entityId}/Business`,
title: 'Create business', title: t('Create business'),
onDataSaved: () => tableRef.reload(), onDataSaved: () => tableRef.reload(),
formInitialData: {}, formInitialData: {},
}" }"
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"
default-mode="card"
auto-load auto-load
:disable-option="{ table: true }" :disable-option="{ card: true }"
:right-search="false" :right-search="false"
card-class="grid-two q-gutter-x-xl q-gutter-y-md q-pr-lg q-py-lg"
:is-editable="true" :is-editable="true"
:use-model="true" :use-model="true"
:right-search-icon="false"
@save-changes="(data) => reactivateWorker(data)" @save-changes="(data) => reactivateWorker(data)"
/> />
</template> </template>
<i18n> <i18n>
es: es:
Do you want to reactivate the user?: desea reactivar el usuario? Do you want to reactivate the user?: desea reactivar el usuario?
Create business: Crear contrato
</i18n> </i18n>

View File

@ -111,6 +111,7 @@ const handlePhotoUpdated = (evt = false) => {
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('globals.user')" :value="entity.user?.name" /> <VnLv :label="t('globals.user')" :value="entity.user?.name" />
<VnLv <VnLv
class="ellipsis-text"
:label="t('globals.params.email')" :label="t('globals.params.email')"
:value="entity.user?.emailUser?.email" :value="entity.user?.emailUser?.email"
copy copy
@ -177,6 +178,12 @@ const handlePhotoUpdated = (evt = false) => {
.photo { .photo {
height: 256px; height: 256px;
} }
.ellipsis-text {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style> </style>
<i18n> <i18n>

View File

@ -12,6 +12,11 @@ const $props = defineProps({
<template> <template>
<QPopupProxy> <QPopupProxy>
<WorkerDescriptor v-if="$props.id" :id="$props.id" :summary="WorkerSummary" /> <WorkerDescriptor
v-if="$props.id"
:id="$props.id"
:summary="WorkerSummary"
data-key="WorkerDescriptorProxy"
/>
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -0,0 +1,24 @@
describe('ClaimNotes', () => {
const descriptorOptions = '[data-cy="descriptor-more-opts-menu"] > .q-list';
const url = '/#/account/1/summary';
it('should see all the account options', () => {
cy.login('itManagement');
cy.visit(url);
cy.dataCy('descriptor-more-opts').click();
cy.get(descriptorOptions)
.find('.q-item')
.its('length')
.then((count) => {
cy.log('Número de opciones:', count);
expect(count).to.equal(5);
});
});
it('should not see any option', () => {
cy.login('salesPerson');
cy.visit(url);
cy.dataCy('descriptor-more-opts').click();
cy.get(descriptorOptions).should('not.be.visible');
});
});

View File

@ -67,7 +67,7 @@ describe('Entry', () => {
it('Should notify when entry is lock by another user', () => { it('Should notify when entry is lock by another user', () => {
const checkLockMessage = () => { const checkLockMessage = () => {
cy.get('[data-cy="entry-lock-confirm"]').should('be.visible'); cy.get('[role="dialog"]').should('be.visible');
cy.get('[data-cy="VnConfirm_message"] > span').should( cy.get('[data-cy="VnConfirm_message"] > span').should(
'contain.text', 'contain.text',
'This entry has been locked by buyerNick', 'This entry has been locked by buyerNick',

View File

@ -1,36 +1,33 @@
/// <reference types="cypress" />
describe('Item tag', () => { describe('Item tag', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080); cy.viewport(1920, 1080);
cy.login('developer'); cy.login('developer');
cy.visit(`/#/item/1/tags`); cy.visit(`/#/item/1/tags`);
cy.get('.q-page').should('be.visible');
cy.waitForElement('[data-cy="itemTags"]');
}); });
const createNewTag = 'createNewTag';
const saveBtn = 'crudModelDefaultSaveBtn';
const newTag = 'tagundefined';
it('should throw an error adding an existent tag', () => { it('should throw an error adding an existent tag', () => {
cy.get('.q-page').should('be.visible'); cy.dataCy(createNewTag).click();
cy.get('.q-page-sticky > div').click(); cy.dataCy(newTag).should('be.visible').click().type('Genero{enter}');
cy.get('.q-page-sticky > div').click(); cy.dataCy('tagGeneroValue').eq(1).should('be.visible');
cy.dataCy('Tag_select').eq(7).type('Tallos'); cy.dataCy(saveBtn).click();
cy.get('.q-menu .q-item').contains('Tallos').click();
cy.get(':nth-child(8) > [label="Value"]').type('1');
cy.dataCy('crudModelDefaultSaveBtn').click();
cy.checkNotification("The tag or priority can't be repeated for an item"); cy.checkNotification("The tag or priority can't be repeated for an item");
}); });
it('should add a new tag', () => { it('should add a new tag', () => {
cy.get('.q-page').should('be.visible'); cy.dataCy(createNewTag).click();
cy.get('.q-page-sticky > div').click(); cy.dataCy(newTag).should('be.visible').click().type('Forma{enter}');
cy.get('.q-page-sticky > div').click(); cy.dataCy('tagFormaValue').should('be.visible').type('50');
cy.dataCy('Tag_select').eq(7).click(); cy.dataCy(saveBtn).click();
cy.get('.q-menu .q-item').contains('Ancho de la base').type('{enter}');
cy.get(':nth-child(8) > [label="Value"]').type('50');
cy.dataCy('crudModelDefaultSaveBtn').click();
cy.checkNotification('Data saved'); cy.checkNotification('Data saved');
cy.dataCy('itemTags') cy.dataCy('deleteTagForma').should('be.visible').click();
.children(':nth-child(8)') cy.dataCy('VnConfirm_confirm').should('be.visible').click();
.find('.justify-center > .q-icon')
.click();
cy.dataCy('VnConfirm_confirm').click();
cy.checkNotification('Data saved'); cy.checkNotification('Data saved');
}); });
}); });