#7283 #7831 itemMigration #553

Merged
carlossa merged 77 commits from 7283-itemMigration into dev 2024-10-25 07:09:13 +00:00
142 changed files with 4226 additions and 2147 deletions
Showing only changes of commit 06a1b3b409 - Show all commits

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.40.0", "version": "24.42.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -2,9 +2,15 @@ import { boot } from 'quasar/wrappers';
import qFormMixin from './qformMixin'; import qFormMixin from './qformMixin';
import mainShortcutMixin from './mainShortcutMixin'; import mainShortcutMixin from './mainShortcutMixin';
import keyShortcut from './keyShortcut'; import keyShortcut from './keyShortcut';
import useNotify from 'src/composables/useNotify.js';
const { notify } = useNotify();
export default boot(({ app }) => { export default boot(({ app }) => {
app.mixin(qFormMixin); app.mixin(qFormMixin);
app.mixin(mainShortcutMixin); app.mixin(mainShortcutMixin);
app.directive('shortcut', keyShortcut); app.directive('shortcut', keyShortcut);
app.config.errorHandler = function (err) {
console.error(err);
notify('globals.error', 'negative', 'error');
};
}); });

View File

@ -21,7 +21,7 @@ const customer = computed(() => state.get('customer'));
const bankEntityFormData = reactive({ const bankEntityFormData = reactive({
name: null, name: null,
bic: null, bic: null,
countryFk: customer.value.countryFk, countryFk: customer.value?.countryFk,
id: null, id: null,
}); });

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { reactive, ref } from 'vue'; import { reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -22,9 +22,11 @@ const postcodeFormData = reactive({
townFk: null, townFk: null,
}); });
const townsFetchDataRef = ref(null);
const provincesFetchDataRef = ref(null); const provincesFetchDataRef = ref(null);
const countriesOptions = ref([]); const countriesOptions = ref([]);
const provincesOptions = ref([]); const provincesOptions = ref([]);
const townsOptions = ref([]);
const town = ref({}); const town = ref({});
function onDataSaved(formData) { function onDataSaved(formData) {
@ -67,20 +69,62 @@ async function setProvince(id, data) {
data.countryFk = newProvince.countryFk; data.countryFk = newProvince.countryFk;
} }
watch(
() => [postcodeFormData.countryFk],
async (newCountryFk) => {
if (newCountryFk) {
await provincesFetchDataRef.value.fetch({
where: {
countryFk: newCountryFk[0],
},
});
await townsFetchDataRef.value.fetch({
where: {
provinceFk: {
inq: provincesOptions.value.map(({ id }) => id),
},
},
});
}
}
);
watch(
() => postcodeFormData.provinceFk,
async (newProvinceFk) => {
if (newProvinceFk) {
await townsFetchDataRef.value.fetch({
where: { provinceFk: newProvinceFk[0] },
});
}
}
);
async function handleProvinces(data) {
provincesOptions.value = data;
}
async function handleTowns(data) {
townsOptions.value = data;
}
async function handleCountries(data) {
countriesOptions.value = data;
}
</script> </script>
<template> <template>
<FetchData <FetchData
ref="provincesFetchDataRef" ref="provincesFetchDataRef"
@on-fetch="(data) => (provincesOptions = data)" @on-fetch="handleProvinces"
auto-load auto-load
url="Provinces/location" url="Provinces/location"
/> />
<FetchData <FetchData
@on-fetch="(data) => (countriesOptions = data)" ref="townsFetchDataRef"
@on-fetch="handleTowns"
auto-load auto-load
url="Countries" url="Towns/location"
/> />
<FetchData @on-fetch="handleCountries" auto-load url="Countries" />
<FormModelPopup <FormModelPopup
url-create="postcodes" url-create="postcodes"
model="postcode" model="postcode"
@ -99,9 +143,9 @@ async function setProvince(id, data) {
/> />
<VnSelectDialog <VnSelectDialog
:label="t('City')" :label="t('City')"
url="Towns/location"
@update:model-value="(value) => setTown(value, data)" @update:model-value="(value) => setTown(value, data)"
v-model="data.townFk" v-model="data.townFk"
:options="townsOptions"
option-label="name" option-label="name"
option-value="id" option-value="id"
:rules="validate('postcode.city')" :rules="validate('postcode.city')"
@ -132,6 +176,7 @@ async function setProvince(id, data) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelectProvince <VnSelectProvince
:country-fk="postcodeFormData.countryFk"
@update:model-value="(value) => setProvince(value, data)" @update:model-value="(value) => setProvince(value, data)"
v-model="data.provinceFk" v-model="data.provinceFk"
/> />

View File

@ -50,7 +50,7 @@ const onDataSaved = (dataSaved) => {
model="thermograph" model="thermograph"
:title="t('New thermograph')" :title="t('New thermograph')"
:form-initial-data="thermographFormData" :form-initial-data="thermographFormData"
@on-data-saved="onDataSaved($event)" @on-data-saved="(_, response) => onDataSaved(response)"
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow> <VnRow>

View File

@ -234,6 +234,8 @@ async function remove(data) {
newData = newData.filter((form) => !ids.some((id) => id == form[pk])); newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
fetch(newData); fetch(newData);
}); });
} else {
reset();
} }
emit('update:selected', []); emit('update:selected', []);
} }

View File

@ -297,11 +297,12 @@ const removeTag = (index, params, search) => {
/> />
</QItem> </QItem>
<QItem class="q-mt-lg"> <QItem class="q-mt-lg">
<QIcon <QBtn
name="add_circle" icon="add_circle"
shortcut="+"
flat
class="fill-icon-on-hover q-px-xs" class="fill-icon-on-hover q-px-xs"
color="primary" color="primary"
size="sm"
@click="tagValues.push({})" @click="tagValues.push({})"
/> />
</QItem> </QItem>

View File

@ -44,7 +44,6 @@ const itemComputed = computed(() => {
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-item { .q-item {
min-height: 5vh; min-height: 5vh;

View File

@ -13,12 +13,14 @@ import FetchData from 'components/FetchData.vue';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import VnAvatar from './ui/VnAvatar.vue'; import VnAvatar from './ui/VnAvatar.vue';
import useNotify from 'src/composables/useNotify';
const state = useState(); const state = useState();
const session = useSession(); const session = useSession();
const router = useRouter(); const router = useRouter();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { copyText } = useClipboard(); const { copyText } = useClipboard();
const { notify } = useNotify();
const userLocale = computed({ const userLocale = computed({
get() { get() {
@ -53,6 +55,7 @@ const user = state.getUser();
const warehousesData = ref(); const warehousesData = ref();
const companiesData = ref(); const companiesData = ref();
const accountBankData = ref(); const accountBankData = ref();
const isEmployee = computed(() => useRole().isEmployee());
onMounted(async () => { onMounted(async () => {
updatePreferences(); updatePreferences();
@ -70,18 +73,28 @@ function updatePreferences() {
async function saveDarkMode(value) { async function saveDarkMode(value) {
const query = `/UserConfigs/${user.value.id}`; const query = `/UserConfigs/${user.value.id}`;
await axios.patch(query, { try {
darkMode: value, await axios.patch(query, {
}); darkMode: value,
user.value.darkMode = value; });
user.value.darkMode = value;
onDataSaved();
} catch (error) {
onDataError();
}
} }
async function saveLanguage(value) { async function saveLanguage(value) {
const query = `/VnUsers/${user.value.id}`; const query = `/VnUsers/${user.value.id}`;
await axios.patch(query, { try {
lang: value, await axios.patch(query, {
}); lang: value,
user.value.lang = value; });
user.value.lang = value;
onDataSaved();
} catch (error) {
onDataError();
}
} }
function logout() { function logout() {
@ -97,11 +110,23 @@ function localUserData() {
state.setUser(user.value); state.setUser(user.value);
} }
function saveUserData(param, value) { async function saveUserData(param, value) {
axios.post('UserConfigs/setUserConfig', { [param]: value }); try {
localUserData(); await axios.post('UserConfigs/setUserConfig', { [param]: value });
localUserData();
onDataSaved();
} catch (error) {
onDataError();
}
} }
const isEmployee = computed(() => useRole().isEmployee());
const onDataSaved = () => {
notify('globals.dataSaved', 'positive');
};
const onDataError = () => {
notify('errors.updateUserConfig', 'negative');
};
</script> </script>
<template> <template>

View File

@ -10,7 +10,12 @@ import CreateNewProvinceForm from './CreateNewProvinceForm.vue';
const emit = defineEmits(['onProvinceCreated']); const emit = defineEmits(['onProvinceCreated']);
const provinceFk = defineModel({ type: Number }); const provinceFk = defineModel({ type: Number });
watch(provinceFk, async () => await provincesFetchDataRef.value.fetch()); watch(provinceFk, async () => await provincesFetchDataRef.value.fetch());
const $props = defineProps({
countryFk: {
type: Number,
default: null,
},
});
const { validate } = useValidator(); const { validate } = useValidator();
const { t } = useI18n(); const { t } = useI18n();
@ -18,17 +23,30 @@ const provincesOptions = ref();
const provincesFetchDataRef = ref(); const provincesFetchDataRef = ref();
async function onProvinceCreated(_, data) { async function onProvinceCreated(_, data) {
await provincesFetchDataRef.value.fetch(); await provincesFetchDataRef.value.fetch({ where: { countryFk: $props.countryFk } });
provinceFk.value = data.id; provinceFk.value = data.id;
emit('onProvinceCreated', data); emit('onProvinceCreated', data);
} }
watch(
() => $props.countryFk,
async (newProvinceFk) => {
if (newProvinceFk) {
await provincesFetchDataRef.value.fetch({
where: { countryFk: newProvinceFk },
});
}
}
);
async function handleProvinces(data) {
provincesOptions.value = data;
}
</script> </script>
<template> <template>
<FetchData <FetchData
ref="provincesFetchDataRef" ref="provincesFetchDataRef"
:filter="{ include: { relation: 'country' } }" :filter="{ include: { relation: 'country' } }"
@on-fetch="(data) => (provincesOptions = data)" @on-fetch="handleProvinces"
auto-load auto-load
url="Provinces" url="Provinces"
/> />

View File

@ -10,8 +10,6 @@ import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue'; import VnInputTime from 'components/common/VnInputTime.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
defineExpose({ addFilter });
const $props = defineProps({ const $props = defineProps({
column: { column: {
type: Object, type: Object,
@ -30,6 +28,9 @@ const $props = defineProps({
default: 'params', default: 'params',
}, },
}); });
defineExpose({ addFilter, props: $props });
const model = defineModel(undefined, { required: true }); const model = defineModel(undefined, { required: true });
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
const columnFilter = computed(() => $props.column?.columnFilter); const columnFilter = computed(() => $props.column?.columnFilter);
@ -115,11 +116,11 @@ const components = {
rawSelect: selectComponent, rawSelect: selectComponent,
}; };
async function addFilter(value) { async function addFilter(value, name) {
value ??= undefined; value ??= undefined;
if (value && typeof value === 'object') value = model.value; if (value && typeof value === 'object') value = model.value;
value = value === '' ? undefined : value; value = value === '' ? undefined : value;
let field = columnFilter.value?.name ?? $props.column.name; let field = columnFilter.value?.name ?? $props.column.name ?? name;
if (columnFilter.value?.inWhere) { if (columnFilter.value?.inWhere) {
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field; if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;

View File

@ -10,7 +10,7 @@ import FormModelPopup from 'components/FormModelPopup.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
import VnTableFilter from 'components/VnTable/VnFilter.vue'; import VnFilter from 'components/VnTable/VnFilter.vue';
import VnTableChip from 'components/VnTable/VnChip.vue'; import VnTableChip from 'components/VnTable/VnChip.vue';
import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
@ -53,6 +53,10 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
bottom: {
type: Object,
default: null,
},
cardClass: { cardClass: {
type: String, type: String,
default: 'flex-one', default: 'flex-one',
@ -69,10 +73,6 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
disableInfiniteScroll: {
type: Boolean,
default: false,
},
hasSubToolbar: { hasSubToolbar: {
type: Boolean, type: Boolean,
default: null, default: null,
@ -101,10 +101,18 @@ const $props = defineProps({
type: String, type: String,
default: '90vh', default: '90vh',
}, },
chipLocale: {
type: String,
default: null,
},
footer: { footer: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
disabledAttr: {
type: Boolean,
default: false,
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -125,6 +133,7 @@ const showForm = ref(false);
const splittedColumns = ref({ columns: [] }); const splittedColumns = ref({ columns: [] });
const columnsVisibilitySkipped = ref(); const columnsVisibilitySkipped = ref();
const createForm = ref(); const createForm = ref();
const tableFilterRef = ref([]);
const tableModes = [ const tableModes = [
{ {
@ -150,7 +159,7 @@ onMounted(() => {
quasar.platform.is.mobile && !$props.disableOption?.card quasar.platform.is.mobile && !$props.disableOption?.card
? CARD_MODE ? CARD_MODE
: $props.defaultMode; : $props.defaultMode;
stateStore.rightDrawer = true; stateStore.rightDrawer = quasar.screen.gt.xs;
columnsVisibilitySkipped.value = [ columnsVisibilitySkipped.value = [
...splittedColumns.value.columns ...splittedColumns.value.columns
.filter((c) => c.visible == false) .filter((c) => c.visible == false)
@ -227,7 +236,7 @@ function splitColumns(columns) {
if (col.cardVisible) splittedColumns.value.cardVisible.push(col); if (col.cardVisible) splittedColumns.value.cardVisible.push(col);
if ($props.isEditable && col.disable == null) col.disable = false; if ($props.isEditable && col.disable == null) col.disable = false;
if ($props.useModel && col.columnFilter != false) if ($props.useModel && col.columnFilter != false)
col.columnFilter = { ...col.columnFilter, inWhere: true }; col.columnFilter = { inWhere: true, ...col.columnFilter };
splittedColumns.value.columns.push(col); splittedColumns.value.columns.push(col);
} }
// Status column // Status column
@ -305,6 +314,7 @@ defineExpose({
redirect: redirectFn, redirect: redirectFn,
selected, selected,
CrudModelRef, CrudModelRef,
params,
}); });
function handleOnDataSaved(_) { function handleOnDataSaved(_) {
@ -328,6 +338,13 @@ function handleOnDataSaved(_) {
:search-url="searchUrl" :search-url="searchUrl"
:redirect="!!redirect" :redirect="!!redirect"
@set-user-params="setUserParams" @set-user-params="setUserParams"
:disable-submit-event="true"
@remove="
(key) =>
tableFilterRef
.find((f) => f.props?.column.name == key)
?.addFilter()
"
> >
<template #body> <template #body>
<div <div
@ -337,7 +354,8 @@ function handleOnDataSaved(_) {
)" )"
:key="col.id" :key="col.id"
> >
<VnTableFilter <VnFilter
ref="tableFilterRef"
:column="col" :column="col"
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
v-model="params[columnName(col)]" v-model="params[columnName(col)]"
@ -361,10 +379,15 @@ function handleOnDataSaved(_) {
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
/> />
</template> </template>
<template #tags="{ tag, formatFn }" v-if="chipLocale">
<div class="q-gutter-x-xs">
<strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
</VnFilterPanel> </VnFilterPanel>
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<!-- class in div to fix warn-->
<CrudModel <CrudModel
v-bind="$attrs" v-bind="$attrs"
:class="$attrs['class'] ?? 'q-px-md'" :class="$attrs['class'] ?? 'q-px-md'"
@ -372,9 +395,7 @@ function handleOnDataSaved(_) {
ref="CrudModelRef" ref="CrudModelRef"
@on-fetch="(...args) => emit('onFetch', ...args)" @on-fetch="(...args) => emit('onFetch', ...args)"
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll=" :disable-infinite-scroll="isTableMode"
$attrs['disableInfiniteScroll'] ? isTableMode : !disableInfiniteScroll
"
@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']"
@ -394,7 +415,7 @@ function handleOnDataSaved(_) {
card-container-class="grid-three" card-container-class="grid-three"
flat flat
:style="isTableMode && `max-height: ${tableHeight}`" :style="isTableMode && `max-height: ${tableHeight}`"
:virtual-scroll="!isTableMode" :virtual-scroll="isTableMode"
@virtual-scroll=" @virtual-scroll="
(event) => (event) =>
event.index > rows.length - 2 && event.index > rows.length - 2 &&
@ -424,7 +445,7 @@ function handleOnDataSaved(_) {
<QBtn <QBtn
v-if="$props.rightSearch" v-if="$props.rightSearch"
icon="filter_alt" icon="filter_alt"
class="bg-vn-section-color q-ml-md" class="bg-vn-section-color q-ml-sm"
dense dense
@click="stateStore.toggleRightDrawer()" @click="stateStore.toggleRightDrawer()"
/> />
@ -446,7 +467,7 @@ function handleOnDataSaved(_) {
:search-url="searchUrl" :search-url="searchUrl"
/> />
</div> </div>
<VnTableFilter <VnFilter
v-if="$props.columnSearch" v-if="$props.columnSearch"
:column="col" :column="col"
:show-title="true" :show-title="true"
@ -511,8 +532,12 @@ function handleOnDataSaved(_) {
:key="index" :key="index"
:title="btn.title" :title="btn.title"
:icon="btn.icon" :icon="btn.icon"
class="q-px-sm text-primary-light" class="q-pa-xs"
flat flat
dense
:class="
btn.isPrimary ? 'text-primary-light' : 'color-vn-text '
"
:style="`visibility: ${ :style="`visibility: ${
(btn.show && btn.show(row)) ?? true ? 'visible' : 'hidden' (btn.show && btn.show(row)) ?? true ? 'visible' : 'hidden'
}`" }`"
@ -520,6 +545,29 @@ function handleOnDataSaved(_) {
/> />
</QTd> </QTd>
</template> </template>
<template #bottom v-if="bottom">
<slot name="bottom-table">
<QBtn
@click="
() =>
createAsDialog
? (showForm = !showForm)
: handleOnDataSaved(create)
"
class="cursor-pointer fill-icon"
color="primary"
icon="add_circle"
size="md"
round
flat
shortcut="+"
:disabled="!disabledAttr"
/>
<QTooltip>
{{ createForm.title }}
</QTooltip>
</slot>
</template>
<template #item="{ row, colsMap }"> <template #item="{ row, colsMap }">
<component <component
:is="$props.redirect ? 'router-link' : 'span'" :is="$props.redirect ? 'router-link' : 'span'"
@ -636,25 +684,15 @@ function handleOnDataSaved(_) {
</QCard> </QCard>
</component> </component>
</template> </template>
<template #bottom-row="{ cols }" v-if="footer"> <template #bottom-row="{ cols }" v-if="$props.footer">
<QTr v-if="rows.length" class="bg-header" style="height: 30px"> <QTr v-if="rows.length" style="height: 30px">
<QTh <QTh
v-for="col of cols.filter((cols) => cols.visible ?? true)" v-for="col of cols.filter((cols) => cols.visible ?? true)"
:key="col?.id" :key="col?.id"
class="text-center" class="text-center"
:class="getColAlign(col)"
> >
<div <slot :name="`column-footer-${col.name}`" />
v-if="col?.summation"
:class="`text-${col?.align ?? 'left'}`"
class="text-bold q-pa-sm"
>
{{
rows.reduce(
(sum, currentRow) => sum + currentRow[col.name],
0
)
}}
</div>
</QTh> </QTh>
</QTr> </QTr>
</template> </template>
@ -733,16 +771,6 @@ es:
color: var(--vn-text-color); color: var(--vn-text-color);
} }
.q-table--dark .q-table__bottom,
.q-table--dark thead,
.q-table--dark tr {
border-color: var(--vn-section-color);
}
.q-table__container > div:first-child {
background-color: var(--vn-page-color);
}
.grid-three { .grid-three {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, max-content)); grid-template-columns: repeat(auto-fit, minmax(400px, max-content));
@ -771,10 +799,16 @@ es:
} }
} }
.q-table th { .q-table {
padding: 0; th {
} padding: 0;
}
&__top {
padding: 12px 0px;
top: 0;
}
}
.vnTable { .vnTable {
thead tr th { thead tr th {
position: sticky; position: sticky;
@ -810,6 +844,15 @@ es:
background-color: var(--vn-section-color); background-color: var(--vn-section-color);
z-index: 1; z-index: 1;
} }
table tbody th {
position: relative;
}
tbody:nth-last-child(1) {
@extend .bg-header;
position: sticky;
z-index: 2;
bottom: 0;
}
} }
.vn-label-value { .vn-label-value {
@ -855,4 +898,12 @@ es:
cursor: text; cursor: text;
user-select: all; user-select: all;
} }
.q-table__container {
background-color: transparent;
}
.q-table__middle.q-virtual-scroll.q-virtual-scroll--vertical.scroll {
background-color: var(--vn-section-color);
}
</style> </style>

View File

@ -135,7 +135,7 @@ onMounted(async () => {
}); });
</script> </script>
<template> <template>
<QBtn icon="vn:visible_columns" class="bg-vn-section-color q-mr-md q-px-sm" dense> <QBtn icon="vn:visible_columns" class="bg-vn-section-color q-mr-sm q-px-sm" dense>
<QPopupProxy ref="popupProxyRef"> <QPopupProxy ref="popupProxyRef">
<QCard class="column q-pa-md"> <QCard class="column q-pa-md">
<QIcon name="info" size="sm" class="info-icon"> <QIcon name="info" size="sm" class="info-icon">

View File

@ -400,7 +400,14 @@ defineExpose({
/> />
</QDialog> </QDialog>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="showFormDialog()" class="fill-icon"> <QBtn
fab
color="primary"
icon="add"
shortcut="+"
@click="showFormDialog()"
class="fill-icon"
>
<QTooltip> <QTooltip>
{{ t('Upload file') }} {{ t('Upload file') }}
</QTooltip> </QTooltip>

View File

@ -103,6 +103,7 @@ const mixinRules = [
@click=" @click="
() => { () => {
value = null; value = null;
vnInputRef.focus();
emit('remove'); emit('remove');
} }
" "
@ -129,4 +130,30 @@ const mixinRules = [
.q-field__append { .q-field__append {
padding-inline: 0; padding-inline: 0;
} }
.q-field__append.q-field__marginal.row.no-wrap.items-center.row {
height: 20px;
}
.q-field--outlined .q-field__append.q-field__marginal.row.no-wrap.items-center.row {
height: auto;
}
.q-field__control {
height: unset;
}
.q-field__control.relative-position.row.no-wrap
> .q-field__control-container
> input.q-field__native
~ div.q-field__label {
height: 41px;
}
.q-field--labeled {
.q-field__native,
.q-field__prefix,
.q-field__suffix,
.q-field__input {
padding-bottom: 0;
min-height: 15px;
}
}
</style> </style>

View File

@ -20,6 +20,7 @@ const { validations } = useValidator();
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val); const requiredFieldRule = (val) => validations().required($attrs.required, val);
const vnInputDateRef = ref(null);
const dateFormat = 'DD/MM/YYYY'; const dateFormat = 'DD/MM/YYYY';
const isPopupOpen = ref(); const isPopupOpen = ref();
@ -91,6 +92,7 @@ const styleAttrs = computed(() => {
<template> <template>
<div @mouseover="hover = true" @mouseleave="hover = false"> <div @mouseover="hover = true" @mouseleave="hover = false">
<QInput <QInput
ref="vnInputDateRef"
v-model="formattedDate" v-model="formattedDate"
class="vn-input-date" class="vn-input-date"
:mask="mask" :mask="mask"
@ -113,6 +115,7 @@ const styleAttrs = computed(() => {
!$attrs.disable !$attrs.disable
" "
@click=" @click="
vnInputDateRef.focus();
model = null; model = null;
isPopupOpen = false; isPopupOpen = false;
" "

View File

@ -16,6 +16,7 @@ const props = defineProps({
default: false, default: false,
}, },
}); });
const vnInputTimeRef = ref(null);
const initialDate = ref(model.value ?? Date.vnNew()); const initialDate = ref(model.value ?? Date.vnNew());
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => validations().required($attrs.required, val); const requiredFieldRule = (val) => validations().required($attrs.required, val);
@ -69,6 +70,7 @@ function dateToTime(newDate) {
<template> <template>
<div @mouseover="hover = true" @mouseleave="hover = false"> <div @mouseover="hover = true" @mouseleave="hover = false">
<QInput <QInput
ref="vnInputTimeRef"
class="vn-input-time" class="vn-input-time"
mask="##:##" mask="##:##"
placeholder="--:--" placeholder="--:--"
@ -92,6 +94,7 @@ function dateToTime(newDate) {
!$attrs.disable !$attrs.disable
" "
@click=" @click="
vnInputTimeRef.focus();
model = null; model = null;
isPopupOpen = false; isPopupOpen = false;
" "

View File

@ -12,14 +12,43 @@ const props = defineProps({
default: null, default: null,
}, },
}); });
const formatLocation = (obj, properties) => {
const parts = properties.map((prop) => {
if (typeof prop === 'string') {
return obj[prop];
} else if (typeof prop === 'function') {
return prop(obj);
}
return null;
});
const filteredParts = parts.filter(
(part) => part !== null && part !== undefined && part !== ''
);
return filteredParts.join(', ');
};
const locationProperties = [
'postcode',
(obj) =>
obj.city
? `${obj.city}${obj.province?.name ? `(${obj.province.name})` : ''}`
: null,
(obj) => obj.country?.name,
];
const modelValue = ref( const modelValue = ref(
props.location props.location ? formatLocation(props.location, locationProperties) : null
? `${props.location?.postcode} - ${props.location?.city}(${props.location?.province?.name}), ${props.location?.country?.name}`
: null
); );
function showLabel(data) { function showLabel(data) {
return `${data.code} - ${data.town}(${data.province}), ${data.country}`; const dataProperties = [
'code',
(obj) => (obj.town ? `${obj.town}(${obj.province})` : null),
'country',
];
return formatLocation(data, dataProperties);
} }
const handleModelValue = (data) => { const handleModelValue = (data) => {
emit('update:model-value', data); emit('update:model-value', data);
}; };

View File

@ -2,6 +2,7 @@
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { useQuasar } from 'quasar';
const stateStore = useStateStore(); const stateStore = useStateStore();
const $props = defineProps({ const $props = defineProps({
@ -10,7 +11,9 @@ const $props = defineProps({
default: true, default: true,
}, },
}); });
onMounted(() => (stateStore.leftDrawer = $props.leftDrawer)); onMounted(
() => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false)
);
</script> </script>
<template> <template>

View File

@ -38,6 +38,10 @@ const $props = defineProps({
type: [Array], type: [Array],
default: () => [], default: () => [],
}, },
exprBuilder: {
type: Function,
default: null,
},
isClearable: { isClearable: {
type: Boolean, type: Boolean,
default: true, default: true,
@ -179,6 +183,7 @@ async function fetchFilter(val) {
}, {}); }, {});
} else defaultWhere = { [key]: getVal(val) }; } else defaultWhere = { [key]: getVal(val) };
const where = { ...(val ? defaultWhere : {}), ...$props.where }; const where = { ...(val ? defaultWhere : {}), ...$props.where };
$props.exprBuilder && Object.assign(where, $props.exprBuilder(key, val));
const fetchOptions = { where, include, limit }; const fetchOptions = { where, include, limit };
if (fields) fetchOptions.fields = fields; if (fields) fetchOptions.fields = fields;
if (sortBy) fetchOptions.order = sortBy; if (sortBy) fetchOptions.order = sortBy;
@ -278,4 +283,15 @@ const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
.q-field--outlined { .q-field--outlined {
max-width: 100%; max-width: 100%;
} }
.q-field__inner {
.q-field__control {
min-height: auto !important;
display: flex;
align-items: flex-end;
.q-field__native.row {
min-height: auto !important;
}
}
}
</style> </style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { computed } from 'vue';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useAcl } from 'src/composables/useAcl'; import { useAcl } from 'src/composables/useAcl';

View File

@ -47,6 +47,7 @@ let store;
let entity; let entity;
const isLoading = ref(false); const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
const menuRef = ref();
defineExpose({ getData }); defineExpose({ getData });
onBeforeMount(async () => { onBeforeMount(async () => {
@ -170,7 +171,7 @@ const toModule = computed(() =>
<QTooltip> <QTooltip>
{{ t('components.cardDescriptor.moreOptions') }} {{ t('components.cardDescriptor.moreOptions') }}
</QTooltip> </QTooltip>
<QMenu ref="menuRef"> <QMenu :ref="menuRef">
<QList> <QList>
<slot name="menu" :entity="entity" :menu-ref="menuRef" /> <slot name="menu" :entity="entity" :menu-ref="menuRef" />
</QList> </QList>

View File

@ -3,7 +3,6 @@ import { onMounted, ref, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { date } from 'quasar';
import toDate from 'filters/toDate'; import toDate from 'filters/toDate';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
@ -59,7 +58,6 @@ const $props = defineProps({
}); });
defineExpose({ search, sanitizer }); defineExpose({ search, sanitizer });
const emit = defineEmits([ const emit = defineEmits([
'update:modelValue', 'update:modelValue',
'refresh', 'refresh',
@ -187,7 +185,6 @@ async function remove(key) {
} }
function formatValue(value) { function formatValue(value) {
if (value instanceof Date) return date.formatDate(value, 'DD/MM/YYYY');
if (typeof value === 'boolean') return value ? t('Yes') : t('No'); if (typeof value === 'boolean') return value ? t('Yes') : t('No');
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value); if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
@ -214,7 +211,7 @@ function sanitizer(params) {
icon="search" icon="search"
@click="search()" @click="search()"
></QBtn> ></QBtn>
<QForm @submit="search" id="filterPanelForm"> <QForm @submit="search" id="filterPanelForm" @keyup.enter="search()">
<QList dense> <QList dense>
<QItem class="q-mt-xs"> <QItem class="q-mt-xs">
<QItemSection top> <QItemSection top>

View File

@ -119,10 +119,9 @@ watch(
); );
watch( watch(
() => [props.url, props.filter, props.userParams], () => [props.url, props.filter],
([url, filter, userParams]) => mounted.value && fetch({ url, filter, userParams }) ([url, filter]) => mounted.value && fetch({ url, filter })
); );
const addFilter = async (filter, params) => { const addFilter = async (filter, params) => {
await arrayData.addFilter({ filter, params }); await arrayData.addFilter({ filter, params });
}; };
@ -131,9 +130,8 @@ async function fetch(params) {
useArrayData(props.dataKey, params); useArrayData(props.dataKey, params);
arrayData.reset(['filter.skip', 'skip']); arrayData.reset(['filter.skip', 'skip']);
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
if (!store.hasMoreData) { if (!store.hasMoreData) isLoading.value = false;
isLoading.value = false;
}
emit('onFetch', store.data); emit('onFetch', store.data);
return store.data; return store.data;
} }

View File

@ -9,6 +9,7 @@ defineProps({ wrap: { type: Boolean, default: false } });
<style lang="scss" scoped> <style lang="scss" scoped>
.vn-row { .vn-row {
display: flex; display: flex;
align-items: flex-end;
> :deep(*) { > :deep(*) {
flex: 1; flex: 1;
} }

View File

@ -26,7 +26,8 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const params = JSON.parse(query[searchUrl]); const params = JSON.parse(query[searchUrl]);
const filter = params?.filter && JSON.parse(params?.filter ?? '{}'); const filter = params?.filter && JSON.parse(params?.filter ?? '{}');
delete params.filter; delete params.filter;
store.userParams = { ...params, ...store.userParams };
store.userParams = { ...store.userParams, ...params };
store.userFilter = { ...filter, ...store.userFilter }; store.userFilter = { ...filter, ...store.userFilter };
if (filter?.order) store.order = filter.order; if (filter?.order) store.order = filter.order;
} }

View File

@ -1,15 +1,18 @@
import { useSession } from './useSession'; import { useSession } from './useSession';
import axios from 'axios'; import axios from 'axios';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
export function usePrintService() { export function usePrintService() {
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n();
const { getTokenMultimedia } = useSession(); const { getTokenMultimedia } = useSession();
function sendEmail(path, params) { function sendEmail(path, params) {
return axios.post(path, params).then(() => return axios.post(path, params).then(() =>
quasar.notify({ quasar.notify({
message: 'Notification sent', message: t('globals.notificationSent'),
type: 'positive', type: 'positive',
icon: 'check', icon: 'check',
}) })

View File

@ -268,6 +268,7 @@ input::-webkit-inner-spin-button {
max-width: 400px; max-width: 400px;
} }
} }
.edit-photo-btn { .edit-photo-btn {
position: absolute; position: absolute;
right: 12px; right: 12px;
@ -280,3 +281,21 @@ input::-webkit-inner-spin-button {
color: var(--vn-label-color); color: var(--vn-label-color);
text-transform: uppercase; text-transform: uppercase;
} }
.q-date {
&__today {
border: 2px solid $info;
color: $info;
}
}
.q-field__inner {
.q-field__control {
min-height: auto !important;
display: flex;
align-items: flex-end;
padding-bottom: 2px;
.q-field__native.row {
min-height: auto !important;
}
}
}

View File

@ -36,7 +36,6 @@ $color-font-secondary: #777;
.bg-success { .bg-success {
background-color: $positive; background-color: $positive;
} }
.bg-notice { .bg-notice {
background-color: $info; background-color: $info;
} }

View File

@ -4,7 +4,7 @@ export default function toHour(date) {
if (!isValidDate(date)) { if (!isValidDate(date)) {
return '--:--'; return '--:--';
} }
return (new Date(date || '')).toLocaleTimeString([], { return new Date(date || '').toLocaleTimeString([], {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
}); });

View File

@ -103,6 +103,7 @@ globals:
ticket: Ticket ticket: Ticket
campaign: Campaign campaign: Campaign
weight: Weight weight: Weight
error: Ups! Something went wrong
pageTitles: pageTitles:
logIn: Login logIn: Login
addressEdit: Update address addressEdit: Update address
@ -110,7 +111,7 @@ globals:
basicData: Basic data basicData: Basic data
log: Logs log: Logs
parkingList: Parkings list parkingList: Parkings list
agencyList: Agencies list agencyList: Agencies
agency: Agency agency: Agency
workCenters: Work centers workCenters: Work centers
modes: Modes modes: Modes
@ -134,7 +135,7 @@ globals:
fiscalData: Fiscal data fiscalData: Fiscal data
billingData: Billing data billingData: Billing data
consignees: Consignees consignees: Consignees
'address-create': New address address-create: New address
notes: Notes notes: Notes
credits: Credits credits: Credits
greuges: Greuges greuges: Greuges
@ -206,7 +207,7 @@ globals:
roadmap: Roadmap roadmap: Roadmap
stops: Stops stops: Stops
routes: Routes routes: Routes
cmrsList: CMRs list cmrsList: CMRs
RouteList: List RouteList: List
routeCreate: New route routeCreate: New route
RouteRoadmap: Roadmaps RouteRoadmap: Roadmaps
@ -272,6 +273,7 @@ globals:
clientsActionsMonitor: Clients and actions clientsActionsMonitor: Clients and actions
serial: Serial serial: Serial
medical: Mutual medical: Mutual
RouteExtendedList: Router
supplier: Supplier supplier: Supplier
created: Created created: Created
worker: Worker worker: Worker
@ -287,6 +289,8 @@ globals:
createInvoiceIn: Create invoice in createInvoiceIn: Create invoice in
myAccount: My account myAccount: My account
noOne: No one noOne: No one
maxTemperature: Maximum Temperature
minTemperature: Minimum Temperatura
params: params:
clientFk: Client id clientFk: Client id
salesPersonFk: Sales person salesPersonFk: Sales person
@ -301,6 +305,7 @@ errors:
statusBadGateway: It seems that the server has fall down statusBadGateway: It seems that the server has fall down
statusGatewayTimeout: Could not contact the server statusGatewayTimeout: Could not contact the server
userConfig: Error fetching user config userConfig: Error fetching user config
updateUserConfig: Error updating user config
tokenConfig: Error fetching token config tokenConfig: Error fetching token config
writeRequest: The requested operation could not be completed writeRequest: The requested operation could not be completed
login: login:
@ -494,6 +499,8 @@ ticket:
warehouse: Warehouse warehouse: Warehouse
customerCard: Customer card customerCard: Customer card
alias: Alias alias: Alias
ticketList: Ticket List
newOrder: New Order
boxing: boxing:
expedition: Expedition expedition: Expedition
item: Item item: Item
@ -515,6 +522,7 @@ ticket:
landed: Landed landed: Landed
consigneePhone: Consignee phone consigneePhone: Consignee phone
consigneeMobile: Consignee mobile consigneeMobile: Consignee mobile
consigneeAddress: Consignee address
clientPhone: Client phone clientPhone: Client phone
clientMobile: Client mobile clientMobile: Client mobile
consignee: Consignee consignee: Consignee
@ -544,6 +552,11 @@ ticket:
weight: Weight weight: Weight
goTo: Go to goTo: Go to
summaryAmount: Summary summaryAmount: Summary
purchaseRequest: Purchase request
service: Service
description: Description
attender: Attender
ok: Ok
create: create:
client: Client client: Client
address: Address address: Address
@ -567,7 +580,6 @@ invoiceOut:
client: Client client: Client
company: Company company: Company
customerCard: Customer card customerCard: Customer card
ticketList: Ticket List
summary: summary:
issued: Issued issued: Issued
created: Created created: Created
@ -871,6 +883,7 @@ route:
tickets: Tickets tickets: Tickets
log: Log log: Log
autonomous: Autonomous autonomous: Autonomous
RouteExtendedList: Router
cmr: cmr:
list: list:
results: results results: results
@ -900,6 +913,7 @@ supplier:
account: Account account: Account
payMethod: Pay Method payMethod: Pay Method
payDay: Pay Day payDay: Pay Day
country: Country
summary: summary:
responsible: Responsible responsible: Responsible
notes: Notes notes: Notes
@ -998,6 +1012,7 @@ travel:
warehouseOut: Warehouse out warehouseOut: Warehouse out
totalEntries: Total entries totalEntries: Total entries
totalEntriesTooltip: Total entries totalEntriesTooltip: Total entries
daysOnward: Landed days onwards
summary: summary:
confirmed: Confirmed confirmed: Confirmed
entryId: Entry Id entryId: Entry Id
@ -1095,7 +1110,7 @@ item:
isPhotoRequestedTooltip: This item does need a photo isPhotoRequestedTooltip: This item does need a photo
description: Description description: Description
fixedPrice: fixedPrice:
itemId: Item ID itemFk: Item ID
groupingPrice: Grouping price groupingPrice: Grouping price
packingPrice: Packing price packingPrice: Packing price
hasMinPrice: Has min price hasMinPrice: Has min price

View File

@ -63,7 +63,7 @@ globals:
shipped: F. envío shipped: F. envío
totalEntries: Ent. totales totalEntries: Ent. totales
amount: Importe amount: Importe
packages: Bultos packages: Embalajes
download: Descargar download: Descargar
downloadPdf: Descargar PDF downloadPdf: Descargar PDF
selectRows: 'Seleccionar las { numberRows } filas(s)' selectRows: 'Seleccionar las { numberRows } filas(s)'
@ -105,6 +105,7 @@ globals:
ticket: Ticket ticket: Ticket
campaign: Campaña campaign: Campaña
weight: Peso weight: Peso
error: ¡Ups! Algo salió mal
pageTitles: pageTitles:
logIn: Inicio de sesión logIn: Inicio de sesión
addressEdit: Modificar consignatario addressEdit: Modificar consignatario
@ -112,7 +113,7 @@ globals:
basicData: Datos básicos basicData: Datos básicos
log: Historial log: Historial
parkingList: Listado de parkings parkingList: Listado de parkings
agencyList: Listado de agencias agencyList: Agencias
agency: Agencia agency: Agencia
workCenters: Centros de trabajo workCenters: Centros de trabajo
modes: Modos modes: Modos
@ -161,6 +162,7 @@ globals:
dms: Gestión documental dms: Gestión documental
entryCreate: Nueva entrada entryCreate: Nueva entrada
latestBuys: Últimas compras latestBuys: Últimas compras
reserves: Reservas
tickets: Tickets tickets: Tickets
ticketCreate: Nuevo ticket ticketCreate: Nuevo ticket
boxing: Encajado boxing: Encajado
@ -209,12 +211,13 @@ globals:
roadmap: Troncales roadmap: Troncales
stops: Paradas stops: Paradas
routes: Rutas routes: Rutas
cmrsList: Listado de CMRs cmrsList: CMRs
RouteList: Listado RouteList: Listado
routeCreate: Nueva ruta routeCreate: Nueva ruta
RouteRoadmap: Troncales RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal RouteRoadmapCreate: Crear troncal
autonomous: Autónomos autonomous: Autónomos
RouteExtendedList: Enrutador
suppliers: Proveedores suppliers: Proveedores
supplier: Proveedor supplier: Proveedor
supplierCreate: Nuevo proveedor supplierCreate: Nuevo proveedor
@ -265,7 +268,7 @@ globals:
tracking: Estados tracking: Estados
components: Componentes components: Componentes
pictures: Fotos pictures: Fotos
packages: Bultos packages: Embalajes
ldap: LDAP ldap: LDAP
samba: Samba samba: Samba
twoFactor: Doble factor twoFactor: Doble factor
@ -290,6 +293,8 @@ globals:
createInvoiceIn: Crear factura recibida createInvoiceIn: Crear factura recibida
myAccount: Mi cuenta myAccount: Mi cuenta
noOne: Nadie noOne: Nadie
maxTemperature: Temperatura máxima
minTemperature: Temperatura mínima
params: params:
clientFk: Id cliente clientFk: Id cliente
salesPersonFk: Comercial salesPersonFk: Comercial
@ -304,6 +309,7 @@ errors:
statusBadGateway: Parece ser que el servidor ha caído statusBadGateway: Parece ser que el servidor ha caído
statusGatewayTimeout: No se ha podido contactar con el servidor statusGatewayTimeout: No se ha podido contactar con el servidor
userConfig: Error al obtener configuración de usuario userConfig: Error al obtener configuración de usuario
updateUserConfig: Error al actualizar la configuración de usuario
tokenConfig: Error al obtener configuración de token tokenConfig: Error al obtener configuración de token
writeRequest: No se pudo completar la operación solicitada writeRequest: No se pudo completar la operación solicitada
login: login:
@ -484,7 +490,7 @@ ticket:
tracking: Estados tracking: Estados
components: Componentes components: Componentes
pictures: Fotos pictures: Fotos
packages: Bultos packages: Embalajes
list: list:
nickname: Alias nickname: Alias
state: Estado state: Estado
@ -502,6 +508,8 @@ ticket:
warehouse: Almacén warehouse: Almacén
customerCard: Ficha del cliente customerCard: Ficha del cliente
alias: Alias alias: Alias
ticketList: Listado de tickets
newOrder: Nuevo pedido
boxing: boxing:
expedition: Expedición expedition: Expedición
item: Artículo item: Artículo
@ -523,6 +531,7 @@ ticket:
landed: Entregado landed: Entregado
consigneePhone: Tel. consignatario consigneePhone: Tel. consignatario
consigneeMobile: Móv. consignatario consigneeMobile: Móv. consignatario
consigneeAddress: Dir. consignatario
clientPhone: Tel. cliente clientPhone: Tel. cliente
clientMobile: Móv. cliente clientMobile: Móv. cliente
consignee: Consignatario consignee: Consignatario
@ -552,6 +561,10 @@ ticket:
weight: Peso weight: Peso
goTo: Ir a goTo: Ir a
summaryAmount: Resumen summaryAmount: Resumen
purchaseRequest: Petición de compra
service: Servicio
description: Descripción
attender: Consignatario
create: create:
client: Cliente client: Cliente
address: Dirección address: Dirección
@ -884,6 +897,7 @@ supplier:
account: Cuenta account: Cuenta
payMethod: Método de pago payMethod: Método de pago
payDay: Dia de pago payDay: Dia de pago
country: País
summary: summary:
responsible: Responsable responsible: Responsable
notes: Notas notes: Notas
@ -982,6 +996,7 @@ travel:
warehouseOut: Alm.entrada warehouseOut: Alm.entrada
totalEntries: totalEntries:
totalEntriesTooltip: Entradas totales totalEntriesTooltip: Entradas totales
daysOnward: Días de llegada en adelante
summary: summary:
confirmed: Confirmado confirmed: Confirmado
entryId: Id entrada entryId: Id entrada
@ -1079,7 +1094,7 @@ item:
isPhotoRequestedTooltip: Este artículo necesita una foto isPhotoRequestedTooltip: Este artículo necesita una foto
description: Descripción description: Descripción
fixedPrice: fixedPrice:
itemId: ID Artículo itemFk: ID Artículo
groupingPrice: Precio grouping groupingPrice: Precio grouping
packingPrice: Precio packing packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo hasMinPrice: Tiene precio mínimo

View File

@ -142,7 +142,13 @@ const redirectToRoleSummary = (id) =>
<SubRoleCreateForm @on-submit-create-subrole="createSubRole" /> <SubRoleCreateForm @on-submit-create-subrole="createSubRole" />
</QDialog> </QDialog>
<QPageSticky position="bottom-right" :offset="[18, 18]"> <QPageSticky position="bottom-right" :offset="[18, 18]">
<QBtn fab icon="add" color="primary" @click="openCreateSubRoleForm()"> <QBtn
fab
icon="add"
shortcut="+"
color="primary"
@click="openCreateSubRoleForm()"
>
<QTooltip>{{ t('warehouses.add') }}</QTooltip> <QTooltip>{{ t('warehouses.add') }}</QTooltip>
</QBtn> </QBtn>
</QPageSticky> </QPageSticky>

View File

@ -317,7 +317,7 @@ async function saveWhenHasChanges() {
</div> </div>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="showImportDialog()" /> <QBtn fab color="primary" shortcut="+" icon="add" @click="showImportDialog()" />
</QPageSticky> </QPageSticky>
</template> </template>

View File

@ -246,7 +246,13 @@ function onDrag() {
</QDialog> </QDialog>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<label for="fileInput"> <label for="fileInput">
<QBtn fab @click="inputFile.nativeEl.click()" icon="add" color="primary"> <QBtn
fab
@click="inputFile.nativeEl.click()"
shortcut="+"
icon="add"
color="primary"
>
<QInput <QInput
ref="inputFile" ref="inputFile"
type="file" type="file"

View File

@ -16,7 +16,7 @@ import { useVnConfirm } from 'composables/useVnConfirm';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnSubToolbar from 'components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
import VnFilter from 'components/VnTable/VnFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue'; import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
@ -33,9 +33,9 @@ const stateStore = useStateStore();
const user = state.getUser(); const user = state.getUser();
const clientRisk = ref([]); const clientRisk = ref([]);
const companies = ref([]);
const tableRef = ref(); const tableRef = ref();
const companyId = ref(user.value.companyFk); const companyId = ref(user.value.companyFk);
const companyLastId = ref(user.value.companyFk);
const balances = ref([]); const balances = ref([]);
const vnFilterRef = ref({}); const vnFilterRef = ref({});
const filter = computed(() => { const filter = computed(() => {
@ -45,33 +45,6 @@ const filter = computed(() => {
}; };
}); });
const companyFilterColumn = {
align: 'left',
name: 'companyId',
label: t('Company'),
component: 'select',
attrs: {
url: 'Companies',
optionLabel: 'code',
sortBy: 'code',
limit: 0,
},
columnFilter: {
event: {
remove: () => (companyId.value = null),
'update:modelValue': (newCompanyFk) => {
if (!newCompanyFk) return;
vnFilterRef.value.addFilter(newCompanyFk);
companyLastId.value = newCompanyFk;
},
blur: () =>
!companyId.value &&
(companyId.value = companyLastId.value ?? user.value.companyFk),
},
},
visible: false,
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'right', align: 'right',
@ -166,6 +139,11 @@ onBeforeMount(() => {
}); });
async function getCurrentBalance(data) { async function getCurrentBalance(data) {
currentBalance.value[companyId.value] = {
amount: 0,
code: companies.value.find((c) => c.id === companyId.value)?.code,
};
for (const balance of data) { for (const balance of data) {
currentBalance.value[balance.companyFk] = { currentBalance.value[balance.companyFk] = {
code: balance.company.code, code: balance.company.code,
@ -192,14 +170,21 @@ const showBalancePdf = ({ id }) => {
<template> <template>
<FetchData <FetchData
url="Companies"
auto-load
@on-fetch="(data) => (companies = data)"
></FetchData>
<FetchData
v-if="companies.length > 0"
url="clientRisks" url="clientRisks"
:filter="{ :filter="{
include: { relation: 'company', scope: { fields: ['code'] } }, include: { relation: 'company', scope: { fields: ['code'] } },
where: { clientFk: route.params.id, companyFk: companyId }, where: { clientFk: route.params.id },
}" }"
auto-load auto-load
@on-fetch="getCurrentBalance" @on-fetch="getCurrentBalance"
></FetchData> ></FetchData>
<VnSubToolbar class="q-mb-md"> <VnSubToolbar class="q-mb-md">
<template #st-data> <template #st-data>
<div class="column justify-center q-px-md q-py-sm"> <div class="column justify-center q-px-md q-py-sm">
@ -212,13 +197,15 @@ const showBalancePdf = ({ id }) => {
</template> </template>
<template #st-actions> <template #st-actions>
<div> <div>
<VnFilter <VnSelect
:label="t('Company')"
ref="vnFilterRef" ref="vnFilterRef"
v-model="companyId" v-model="companyId"
data-key="CustomerBalance" data-key="CustomerBalance"
:column="companyFilterColumn" :options="companies"
search-url="balance" option-label="code"
/> option-value="id"
></VnSelect>
</div> </div>
</template> </template>
</VnSubToolbar> </VnSubToolbar>

View File

@ -16,6 +16,19 @@ const { t } = useI18n();
const businessTypes = ref([]); const businessTypes = ref([]);
const contactChannels = ref([]); const contactChannels = ref([]);
const title = ref(); const title = ref();
const handleSalesModelValue = (val) => ({
or: [
{ name: val },
{ nickname: { like: '%' + val + '%' } },
{ code: { like: `${val}%` } },
],
});
const exprBuilder = (param, value) => {
return {
and: [{ active: { neq: false } }, handleSalesModelValue(value)],
};
};
</script> </script>
<template> <template>
<FetchData <FetchData
@ -34,7 +47,7 @@ const title = ref();
<VnRow> <VnRow>
<VnInput <VnInput
:label="t('globals.name')" :label="t('globals.name')"
:rules="validate('client.socialName')" :rules="validate('client.name')"
autofocus autofocus
clearable clearable
v-model="data.name" v-model="data.name"
@ -98,8 +111,10 @@ const title = ref();
}" }"
:fields="['id', 'nickname']" :fields="['id', 'nickname']"
sort-by="nickname ASC" sort-by="nickname ASC"
option-label="nickname"
option-value="id"
:rules="validate('client.salesPersonFk')" :rules="validate('client.salesPersonFk')"
:use-like="false" :expr-builder="exprBuilder"
emit-value emit-value
auto-load auto-load
> >

View File

@ -0,0 +1,177 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { QItem } from 'quasar';
import VnSelect from 'src/components/common/VnSelect.vue';
import { QItemSection } from 'quasar';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import { toDate } from 'src/filters';
const { t } = useI18n();
defineProps({ dataKey: { type: String, required: true } });
</script>
<template>
<VnFilterPanel :data-key="dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem>
<QItemSection>
<VnInput
:label="t('params.item')"
v-model="params.itemId"
is-outlined
lazy-rules
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.buyerId"
url="TicketRequests/getItemTypeWorker"
:fields="['id', 'nickname']"
sort-by="nickname ASC"
:label="t('params.buyer')"
option-value="id"
option-label="nickname"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.typeId"
url="ItemTypes"
:include="['category']"
:fields="['id', 'name', 'categoryFk']"
sort-by="name ASC"
:label="t('params.typeId')"
option-label="name"
option-value="id"
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>{{
scope.opt?.category?.name
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.categoryId"
url="ItemCategories"
:fields="['id', 'name']"
sort-by="name ASC"
:label="t('params.categoryId')"
option-label="name"
option-value="id"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.campaignId"
url="Campaigns/latest"
sort-by="dated DESC"
:label="t('params.campaignId')"
option-label="code"
option-value="id"
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
t(`params.${scope.opt?.code}`)
}}</QItemLabel>
<QItemLabel caption>{{
toDate(scope.opt.dated)
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.from')"
v-model="params.from"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.to')"
v-model="params.to"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
item: Item id
buyer: Buyer
type: Type
category: Category
itemId: Item id
buyerId: Buyer
typeId: Type
categoryId: Category
from: From
to: To
campaignId: Campaña
valentinesDay: Valentine's Day
mothersDay: Mother's Day
allSaints: All Saints' Day
es:
params:
item: Id artículo
buyer: Comprador
type: Tipo
category: Categoría
itemId: Id Artículo
buyerId: Comprador
typeId: Tipo
categoryId: Reino
from: Desde
to: Hasta
campaignId: Campaña
valentinesDay: Día de San Valentín
mothersDay: Día de la Madre
allSaints: Día de Todos los Santos
</i18n>

View File

@ -56,17 +56,18 @@ const customerContactsRef = ref(null);
</div> </div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<QIcon <QBtn
@click="customerContactsRef.insert()" @click="customerContactsRef.insert()"
class="cursor-pointer" class="cursor-pointer"
color="primary" color="primary"
name="add" flat
size="sm" icon="add"
shortcut="+"
> >
<QTooltip> <QTooltip>
{{ t('Add contact') }} {{ t('Add contact') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</VnRow> </VnRow>
</QCard> </QCard>
</template> </template>

View File

@ -190,6 +190,18 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
> >
<QTooltip>{{ t('Go to user') }}</QTooltip> <QTooltip>{{ t('Go to user') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
v-if="entity.supplier"
:to="{
name: 'SupplierSummary',
params: { id: entity.supplier.id },
}"
size="md"
icon="vn:supplier"
color="primary"
>
<QTooltip>{{ t('Go to supplier') }}</QTooltip>
</QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </CardDescriptor>
@ -204,6 +216,7 @@ es:
Customer ticket list: Listado de tickets del cliente Customer ticket list: Listado de tickets del cliente
Customer invoice out list: Listado de facturas del cliente Customer invoice out list: Listado de facturas del cliente
Go to user: Ir al usuario Go to user: Ir al usuario
Go to supplier: Ir al proveedor
Customer unpaid: Cliente impago Customer unpaid: Cliente impago
Unpaid: Impagado Unpaid: Impagado
unpaidDated: 'Fecha {dated}' unpaidDated: 'Fecha {dated}'

View File

@ -236,6 +236,7 @@ const toCustomerFileManagementCreate = () => {
@click.stop="toCustomerFileManagementCreate()" @click.stop="toCustomerFileManagementCreate()"
color="primary" color="primary"
fab fab
shortcut="+"
icon="add" icon="add"
/> />
<QTooltip> <QTooltip>

View File

@ -134,15 +134,17 @@ function handleLocation(data, location) {
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
<QCheckbox :label="t('Verified data')" v-model="data.isTaxDataChecked" /> <QCheckbox :label="t('Daily invoice')" v-model="data.hasDailyInvoice" />
</VnRow> </VnRow>
<VnRow> <VnRow>
<QCheckbox <QCheckbox
:label="t('Electronic invoice')" :label="t('Electronic invoice')"
v-model="data.hasElectronicInvoice" v-model="data.hasElectronicInvoice"
/><QCheckbox
:label="t('Verified data')"
v-model="data.isTaxDataChecked"
/> />
<QCheckbox :label="t('Daily invoice')" v-model="data.hasDailyInvoice" />
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -99,7 +99,13 @@ const tableRef = ref();
</VnTable> </VnTable>
<QPageSticky :offset="[18, 18]"> <QPageSticky :offset="[18, 18]">
<QBtn @click.stop="toCustomerSamplesCreate()" color="primary" fab icon="add" /> <QBtn
@click.stop="toCustomerSamplesCreate()"
color="primary"
fab
icon="add"
shortcut="+"
/>
<QTooltip> <QTooltip>
{{ t('Send sample') }} {{ t('Send sample') }}
</QTooltip> </QTooltip>

View File

@ -1,35 +1,20 @@
<script setup> <script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ defineProps({
dataKey: { dataKey: {
type: String, type: String,
required: true, required: true,
}, },
}); });
const provinces = ref();
const workers = ref();
const zones = ref();
</script> </script>
<template> <template>
<FetchData url="Provinces" @on-fetch="(data) => (provinces = data)" auto-load /> <VnFilterPanel :data-key="dataKey" :search-button="true" search-url="table">
<FetchData url="Zones" @on-fetch="(data) => (zones = data)" auto-load />
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table">
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong> <strong>{{ t(`params.${tag.label}`) }}: </strong>
@ -65,15 +50,14 @@ const zones = ref();
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection v-if="!workers"> <QItemSection>
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect <VnSelect
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
auto-load
:label="t('Salesperson')" :label="t('Salesperson')"
v-model="params.salesPersonFk" v-model="params.salesPersonFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="workers"
option-value="id" option-value="id"
option-label="name" option-label="name"
emit-value emit-value
@ -88,15 +72,12 @@ const zones = ref();
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection v-if="!provinces"> <QItemSection
<QSkeleton type="QInput" class="full-width" /> ><VnSelect
</QItemSection> url="Provinces"
<QItemSection v-if="provinces">
<VnSelect
:label="t('Province')" :label="t('Province')"
v-model="params.provinceFk" v-model="params.provinceFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="provinces"
option-value="id" option-value="id"
option-label="name" option-label="name"
emit-value emit-value
@ -105,6 +86,7 @@ const zones = ref();
dense dense
outlined outlined
rounded rounded
auto-load
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>
@ -135,25 +117,21 @@ const zones = ref();
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection v-if="!zones"> <VnSelect
<QSkeleton type="QInput" class="full-width" /> url="Zones"
</QItemSection> :label="t('Zone')"
<QItemSection v-if="zones"> v-model="params.zoneFk"
<VnSelect @update:model-value="searchFn()"
:label="t('Zone')" option-value="id"
v-model="params.zoneFk" option-label="name"
@update:model-value="searchFn()" emit-value
:options="zones" map-options
option-value="id" hide-selected
option-label="name" dense
emit-value outlined
map-options rounded
hide-selected auto-load
dense />
outlined
rounded
/>
</QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>

View File

@ -8,10 +8,10 @@ import VnLocation from 'src/components/common/VnLocation.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import CustomerSummary from './Card/CustomerSummary.vue'; import CustomerSummary from './Card/CustomerSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import CustomerFilter from './CustomerFilter.vue';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
@ -397,6 +397,11 @@ function handleLocation(data, location) {
:label="t('Search customer')" :label="t('Search customer')"
data-key="Customer" data-key="Customer"
/> />
<RightMenu>
<template #right-panel>
<CustomerFilter data-key="Customer" />
</template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="Customer" data-key="Customer"
@ -413,6 +418,7 @@ function handleLocation(data, location) {
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"
redirect="customer" redirect="customer"
:right-search="false"
auto-load auto-load
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
@ -423,9 +429,10 @@ function handleLocation(data, location) {
:params="{ :params="{
departmentCodes: ['VT', 'shopping'], departmentCodes: ['VT', 'shopping'],
}" }"
option-label="nickname"
option-value="id"
:fields="['id', 'nickname']" :fields="['id', 'nickname']"
sort-by="nickname ASC" sort-by="nickname ASC"
:use-like="false"
emit-value emit-value
auto-load auto-load
> >

View File

@ -134,6 +134,7 @@ function handleLocation(data, location) {
option-label="fiscalName" option-label="fiscalName"
option-value="id" option-value="id"
v-model="data.customsAgentFk" v-model="data.customsAgentFk"
:tooltip="t('Create a new expense')"
> >
<template #form> <template #form>
<CustomerNewCustomsAgent @on-data-saved="refreshData()" /> <CustomerNewCustomsAgent @on-data-saved="refreshData()" />

View File

@ -11,7 +11,7 @@ import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -226,9 +226,10 @@ function handleLocation(data, location) {
option-label="fiscalName" option-label="fiscalName"
option-value="id" option-value="id"
v-model="data.customsAgentFk" v-model="data.customsAgentFk"
:tooltip="t('New customs agent')"
> >
<template #form> <template #form>
<CustomsNewCustomsAgent /> <CustomerNewCustomsAgent />
</template> </template>
</VnSelectDialog> </VnSelectDialog>
</div> </div>
@ -272,16 +273,17 @@ function handleLocation(data, location) {
</div> </div>
</VnRow> </VnRow>
<QIcon <QBtn
@click.stop="addNote()" @click.stop="addNote()"
class="cursor-pointer add-icon q-mt-md" class="cursor-pointer add-icon q-mt-md"
name="add" flat
size="sm" icon="add"
shortcut="+"
> >
<QTooltip> <QTooltip>
{{ t('Add note') }} {{ t('Add note') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</template> </template>
</FormModel> </FormModel>
</template> </template>
@ -308,6 +310,7 @@ es:
Mobile: Movíl Mobile: Movíl
Incoterms: Incoterms Incoterms: Incoterms
Customs agent: Agente de aduanas Customs agent: Agente de aduanas
New customs agent: Nuevo agente de aduanas
Notes: Notas Notes: Notas
Observation type: Tipo de observación Observation type: Tipo de observación
Description: Descripción Description: Descripción

View File

@ -46,7 +46,6 @@ const onSubmit = async () => {
}; };
try { try {
await axios.patch(`Clients/${$props.id}/setPassword`, payload); await axios.patch(`Clients/${$props.id}/setPassword`, payload);
await $props.promise();
} catch (error) { } catch (error) {
notify('errors.create', 'negative'); notify('errors.create', 'negative');
} finally { } finally {

View File

@ -35,6 +35,7 @@ const filter = {
], ],
where: { clientFk: route.params.id }, where: { clientFk: route.params.id },
order: ['shipped DESC', 'id'], order: ['shipped DESC', 'id'],
limit: 30,
}; };
const columns = computed(() => [ const columns = computed(() => [
@ -149,7 +150,6 @@ const setShippedColor = (date) => {
auto-load auto-load
order="shipped DESC, id" order="shipped DESC, id"
:disable-option="{ card: true, table: true }" :disable-option="{ card: true, table: true }"
limit="5"
class="full-width" class="full-width"
:disable-infinite-scroll="true" :disable-infinite-scroll="true"
> >

View File

@ -58,7 +58,7 @@ customer:
vies: VIES vies: VIES
payMethod: Método de pago payMethod: Método de pago
bankAccount: Cuenta bancaria bankAccount: Cuenta bancaria
dueDay: Día de pago dueDay: Vencimiento
hasLcr: Recibido LCR hasLcr: Recibido LCR
hasCoreVnl: Recibido core VNL hasCoreVnl: Recibido core VNL
hasB2BVnl: Recibido B2B VNL hasB2BVnl: Recibido B2B VNL

View File

@ -138,7 +138,13 @@ const columns = computed(() => [
</template> </template>
</CrudModel> </CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="entryObservationsRef.insert()" /> <QBtn
fab
color="primary"
icon="add"
shortcut="+"
@click="entryObservationsRef.insert()"
/>
</QPageSticky> </QPageSticky>
</template> </template>
<i18n> <i18n>

View File

@ -10,6 +10,7 @@ import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.v
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import axios from 'axios'; import axios from 'axios';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -163,7 +164,7 @@ const fetchEntryBuys = async () => {
> >
<template #header-left> <template #header-left>
<router-link <router-link
v-if="route.name !== 'EntrySummary'" v-if="route?.name !== 'EntrySummary'"
:to="{ name: 'EntrySummary', params: { id: entityId } }" :to="{ name: 'EntrySummary', params: { id: entityId } }"
class="header link" class="header link"
:href="entryUrl" :href="entryUrl"
@ -184,7 +185,10 @@ const fetchEntryBuys = async () => {
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</router-link> </router-link>
<VnLv :label="t('entry.summary.commission')" :value="entry.commission" /> <VnLv :label="t('entry.summary.commission')" :value="entry.commission" />
<VnLv :label="t('entry.summary.currency')" :value="entry.currency.name" /> <VnLv
:label="t('entry.summary.currency')"
:value="entry.currency?.name"
/>
<VnLv :label="t('entry.summary.company')" :value="entry.company.code" /> <VnLv :label="t('entry.summary.company')" :value="entry.company.code" />
<VnLv :label="t('entry.summary.reference')" :value="entry.reference" /> <VnLv :label="t('entry.summary.reference')" :value="entry.reference" />
<VnLv <VnLv
@ -210,12 +214,12 @@ const fetchEntryBuys = async () => {
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('entry.summary.travelAgency')" :label="t('entry.summary.travelAgency')"
:value="entry.travel.agency.name" :value="entry.travel.agency?.name"
/> />
<VnLv :label="t('shipped')" :value="toDate(entry.travel.shipped)" /> <VnLv :label="t('shipped')" :value="toDate(entry.travel.shipped)" />
<VnLv <VnLv
:label="t('entry.summary.travelWarehouseOut')" :label="t('entry.summary.travelWarehouseOut')"
:value="entry.travel.warehouseOut.name" :value="entry.travel.warehouseOut?.name"
/> />
<QCheckbox <QCheckbox
:label="t('entry.summary.travelDelivered')" :label="t('entry.summary.travelDelivered')"
@ -225,7 +229,7 @@ const fetchEntryBuys = async () => {
<VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" /> <VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" />
<VnLv <VnLv
:label="t('entry.summary.travelWarehouseIn')" :label="t('entry.summary.travelWarehouseIn')"
:value="entry.travel.warehouseIn.name" :value="entry.travel.warehouseIn?.name"
/> />
<QCheckbox <QCheckbox
:label="t('entry.summary.travelReceived')" :label="t('entry.summary.travelReceived')"
@ -281,17 +285,17 @@ const fetchEntryBuys = async () => {
> >
<template #body="{ cols, row, rowIndex }"> <template #body="{ cols, row, rowIndex }">
<QTr no-hover> <QTr no-hover>
<QTd v-for="col in cols" :key="col.name"> <QTd v-for="col in cols" :key="col?.name">
<component <component
:is="tableColumnComponents[col.name].component(props)" :is="tableColumnComponents[col?.name].component()"
v-bind="tableColumnComponents[col.name].props(props)" v-bind="tableColumnComponents[col?.name].props()"
@click="tableColumnComponents[col.name].event(props)" @click="tableColumnComponents[col?.name].event()"
class="col-content" class="col-content"
> >
<template <template
v-if=" v-if="
col.name !== 'observation' && col?.name !== 'observation' &&
col.name !== 'isConfirmed' col?.name !== 'isConfirmed'
" "
>{{ col.value }}</template >{{ col.value }}</template
> >

View File

@ -1,16 +1,17 @@
<script setup> <script setup>
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import VnImg from 'src/components/ui/VnImg.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
import { toDate } from 'src/filters';
import VnImg from 'src/components/ui/VnImg.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const columns = [ const columns = [
{ {
align: 'center', align: 'center',

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue'; import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue';
@ -18,6 +19,7 @@ defineProps({
const itemTypeWorkersOptions = ref([]); const itemTypeWorkersOptions = ref([]);
const suppliersOptions = ref([]); const suppliersOptions = ref([]);
const tagValues = ref([]);
</script> </script>
<template> <template>

View File

@ -47,17 +47,16 @@ const columns = [
}, },
}, },
{ {
align: 'left', align: 'center',
label: t('Reserve'), label: t('Reserve'),
name: 'reserve', name: 'reserve',
columnFilter: false, columnFilter: false,
create: true, create: true,
component: 'number', component: 'number',
summation: true, summation: true,
cardVisible: true,
}, },
{ {
align: 'left', align: 'center',
label: t('Bought'), label: t('Bought'),
name: 'bought', name: 'bought',
summation: true, summation: true,
@ -77,7 +76,7 @@ const columns = [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('More'), title: t('View more details'),
icon: 'search', icon: 'search',
isPrimary: true, isPrimary: true,
action: (row) => { action: (row) => {
@ -101,14 +100,9 @@ const travel = ref(null);
const userParams = ref({ const userParams = ref({
dated: Date.vnNew(), dated: Date.vnNew(),
}); });
const filter = ref({ const filter = ref({
where: { fields: ['id', 'm3', 'warehouseInFk'],
shipped: (userParams.value.dated
? new Date(userParams.value.dated)
: Date.vnNew()
).setHours(0, 0, 0, 0),
m3: { neq: null },
},
include: [ include: [
{ {
relation: 'warehouseIn', relation: 'warehouseIn',
@ -117,6 +111,13 @@ const filter = ref({
}, },
}, },
], ],
where: {
shipped: (userParams.value.dated
? new Date(userParams.value.dated)
: Date.vnNew()
).setHours(0, 0, 0, 0),
m3: { neq: null },
},
}); });
const setUserParams = async ({ dated }) => { const setUserParams = async ({ dated }) => {
@ -128,6 +129,22 @@ const setUserParams = async ({ dated }) => {
function openDialog() { function openDialog() {
travelDialogRef.value = true; travelDialogRef.value = true;
} }
function setFooter(data) {
const footer = {
bought: 0,
reserve: 0,
};
data.forEach((row) => {
footer.bought += row?.bought;
footer.reserve += row?.reserve;
});
tableRef.value.footer = footer;
}
function round(value) {
return Math.round(value * 100) / 100;
}
</script> </script>
<template> <template>
<VnSubToolbar> <VnSubToolbar>
@ -139,7 +156,9 @@ function openDialog() {
:filter="filter" :filter="filter"
@on-fetch=" @on-fetch="
(data) => { (data) => {
travel = data.filter((data) => data.warehouseIn.code === 'VNH'); travel = data.find(
(data) => data.warehouseIn.code.toLowerCase() === 'vnh'
);
} }
" "
/> />
@ -149,10 +168,10 @@ function openDialog() {
{{ t('Booked trucks') }}: {{ t('Booked trucks') }}:
</span> </span>
<span> <span>
{{ travel[0]?.m3 }} {{ travel?.m3 }}
</span> </span>
<QBtn <QBtn
v-if="travel[0]?.m3" v-if="travel?.m3"
style="max-width: 20%" style="max-width: 20%"
flat flat
icon="edit" icon="edit"
@ -166,10 +185,10 @@ function openDialog() {
</VnSubToolbar> </VnSubToolbar>
<QDialog v-model="travelDialogRef" :maximized="true" :class="['vn-row', 'wrap']"> <QDialog v-model="travelDialogRef" :maximized="true" :class="['vn-row', 'wrap']">
<FormModelPopup <FormModelPopup
:url-update="`Travels/${travel[0].id}`" :url-update="`Travels/${travel?.id}`"
model="travel" model="travel"
:title="t('Travel m3')" :title="t('Travel m3')"
:form-initial-data="{ id: travel[0].id, m3: travel[0].m3 }" :form-initial-data="{ id: travel?.id, m3: travel?.m3 }"
@on-data-saved="fetchDataRef.fetch()" @on-data-saved="fetchDataRef.fetch()"
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
@ -192,51 +211,81 @@ function openDialog() {
/> />
</template> </template>
</RightMenu> </RightMenu>
<QPage class="column items-center q-pa-md"> <div class="table-container">
<VnTable <div class="column items-center">
ref="tableRef" <VnTable
data-key="StockBoughts" ref="tableRef"
url="StockBoughts/getStockBought" data-key="StockBoughts"
save-url="StockBoughts/crud" url="StockBoughts/getStockBought"
order="reserve DESC" save-url="StockBoughts/crud"
:right-search="false" order="reserve DESC"
:is-editable="true" :right-search="false"
:create="{ :is-editable="true"
urlCreate: 'StockBoughts', @on-fetch="(data) => setFooter(data)"
title: t('Reserve some space'), :create="{
onDataSaved: () => tableRef.reload(), urlCreate: 'StockBoughts',
formInitialData: { title: t('Reserve some space'),
workerFk: user.id, onDataSaved: () => tableRef.reload(),
dated: Date.vnNow(), formInitialData: {
}, workerFk: user.id,
}" dated: Date.vnNow(),
:columns="columns" },
:user-params="userParams" }"
:footer="true" :columns="columns"
auto-load :user-params="userParams"
> :footer="true"
<template #column-workerFk="{ row }"> table-height="80vh"
<span class="link" @click.stop> auto-load
{{ row?.worker?.user?.name }} >
<WorkerDescriptorProxy :id="row?.workerFk" /> <template #column-workerFk="{ row }">
</span> <span class="link" @click.stop>
</template> {{ row?.worker?.user?.name }}
</VnTable> <WorkerDescriptorProxy :id="row?.workerFk" />
</QPage> </span>
</template>
<template #column-bought="{ row }">
<span :class="{ 'text-negative': row.reserve < row.bought }">
{{ row?.bought }}
</span>
</template>
<template #column-footer-reserve>
<span>
{{ round(tableRef.footer.reserve) }}
</span>
</template>
<template #column-footer-bought>
<span
:class="{
'text-negative':
tableRef.footer.reserve < tableRef.footer.bought,
}"
>
{{ round(tableRef.footer.bought) }}
</span>
</template>
</VnTable>
</div>
</div>
</template> </template>
<style lang="css" scoped> <style lang="scss" scoped>
.travel { .travel {
margin-bottom: 0px; margin-bottom: 0px;
} }
.table-container {
display: flex;
justify-content: center;
}
.column {
display: flex;
flex-direction: column;
align-items: center;
width: 35%;
}
.text-negative {
color: $negative !important;
}
</style> </style>
<i18n> <i18n>
en:
Buyer: Buyer
Reserve: Reserve
Bought: Bought
More: More
Date: Date
This buyer has already made a reservation for this date: This buyer has already made a reservation for this date
es: es:
Edit travel: Editar envío Edit travel: Editar envío
Travel: Envíos Travel: Envíos
@ -244,8 +293,8 @@ function openDialog() {
Buyer: Comprador Buyer: Comprador
Reserve: Reservado Reserve: Reservado
Bought: Comprado Bought: Comprado
More: Más
Date: Fecha Date: Fecha
View more details: Ver más detalles
Reserve some space: Reservar espacio Reserve some space: Reservar espacio
This buyer has already made a reservation for this date: Este comprador ya ha hecho una reserva para esta fecha This buyer has already made a reservation for this date: Este comprador ya ha hecho una reserva para esta fecha
</i18n> </i18n>

View File

@ -22,7 +22,7 @@ const customUrl = `StockBoughts/getStockBoughtDetail?workerFk=${$props.workerFk}
const columns = [ const columns = [
{ {
align: 'left', align: 'left',
label: 'Entry', label: t('Entry'),
name: 'entryFk', name: 'entryFk',
isTitle: true, isTitle: true,
isId: true, isId: true,
@ -41,29 +41,33 @@ const columns = [
create: true, create: true,
columnClass: 'expand', columnClass: 'expand',
columnFilter: false, columnFilter: false,
cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
name: 'volume', name: 'volume',
label: t('Volume'), label: t('Volume'),
columnFilter: false, columnFilter: false,
cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
label: t('Packaging'), label: t('Packaging'),
name: 'packagingFk', name: 'packagingFk',
columnFilter: false, columnFilter: false,
cardVisible: true,
}, },
{ {
align: 'left', align: 'left',
label: 'Packing', label: 'Packing',
name: 'packing', name: 'packing',
columnFilter: false, columnFilter: false,
cardVisible: true,
}, },
]; ];
</script> </script>
<template> <template>
<QDialog :maximized="true"> <QDialog>
<div class="container"> <div class="container">
<VnTable <VnTable
ref="tableRef" ref="tableRef"
@ -78,13 +82,13 @@ const columns = [
auto-load auto-load
> >
<template #column-entryFk="{ row }"> <template #column-entryFk="{ row }">
<span class="link" @click.stop> <span class="link">
{{ row?.entryFk }} {{ row?.entryFk }}
<EntryDescriptorProxy :id="row.entryFk" /> <EntryDescriptorProxy :id="row.entryFk" />
</span> </span>
</template> </template>
<template #column-itemName="{ row }"> <template #column-itemName="{ row }">
<span class="link" @click.stop> <span class="link">
{{ row?.itemName }} {{ row?.itemName }}
<ItemDescriptorProxy :id="row.itemFk" /> <ItemDescriptorProxy :id="row.itemFk" />
</span> </span>
@ -93,14 +97,18 @@ const columns = [
</div> </div>
</QDialog> </QDialog>
</template> </template>
<style lang="css"> <style lang="css" scoped>
.q-dialog__inner { .container {
max-width: 50vw; max-width: 50vw;
overflow: auto; overflow: auto;
display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin: auto; margin: auto;
background-color: var(--vn-section-color);
padding: 4px;
}
.container > div > div > .q-table__top.relative-position.row.items-center {
background-color: red !important;
} }
</style> </style>
<i18n> <i18n>
@ -110,4 +118,9 @@ const columns = [
Bought: Comprado Bought: Comprado
More: Más More: Más
Date: Fecha Date: Fecha
Entry: Entrada
Item: Artículo
Name: Nombre
Volume: Volumen
Packaging: Embalage
</i18n> </i18n>

View File

@ -281,6 +281,7 @@ async function onSubmit() {
v-else v-else
icon="add_circle" icon="add_circle"
round round
shortcut="+"
padding="xs" padding="xs"
@click="setCreateDms()" @click="setCreateDms()"
> >

View File

@ -230,7 +230,7 @@ async function insert() {
</template> </template>
</CrudModel> </CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn color="primary" icon="add" size="lg" round @click="insert" /> <QBtn color="primary" icon="add" shortcut="+" size="lg" round @click="insert" />
</QPageSticky> </QPageSticky>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -224,6 +224,7 @@ const formatOpt = (row, { model, options }, prop) => {
<QBtn <QBtn
color="primary" color="primary"
icon="add" icon="add"
shortcut="+"
size="lg" size="lg"
round round
@click="invoiceInFormRef.insert()" @click="invoiceInFormRef.insert()"

View File

@ -405,6 +405,7 @@ const formatOpt = (row, { model, options }, prop) => {
color="primary" color="primary"
icon="add" icon="add"
size="lg" size="lg"
shortcut="+"
round round
@click="invoiceInFormRef.insert()" @click="invoiceInFormRef.insert()"
> >

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue'; import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
import InvoiceOutSummary from './InvoiceOutSummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -10,6 +11,10 @@ const $props = defineProps({
</script> </script>
<template> <template>
<QPopupProxy> <QPopupProxy>
<InvoiceOutDescriptor v-if="$props.id" :id="$props.id" /> <InvoiceOutDescriptor
v-if="$props.id"
:id="$props.id"
:summary="InvoiceOutSummary"
/>
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -27,13 +27,16 @@ const { openReport } = usePrintService();
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'center',
name: 'id', name: 'id',
label: t('invoiceOutList.tableVisibleColumns.id'), label: t('invoiceOutList.tableVisibleColumns.id'),
chip: { chip: {
condition: () => true, condition: () => true,
}, },
isId: true, isId: true,
columnFilter: {
name: 'search',
},
}, },
{ {
align: 'left', align: 'left',

View File

@ -65,17 +65,18 @@ const focusLastInput = () => {
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
<QIcon <QBtn
@click="insertRow()" @click="insertRow()"
class="cursor-pointer fill-icon-on-hover" class="cursor-pointer fill-icon-on-hover"
color="primary" color="primary"
name="add_circle" icon="add_circle"
size="sm" shortcut="+"
flat
> >
<QTooltip> <QTooltip>
{{ t('Add barcode') }} {{ t('Add barcode') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</QCard> </QCard>
</template> </template>
</CrudModel> </CrudModel>

View File

@ -168,19 +168,20 @@ const insertTag = (rows) => {
</div> </div>
</VnRow> </VnRow>
<VnRow class="justify-center items-center"> <VnRow class="justify-center items-center">
<QIcon <QBtn
@click="insertTag(rows)" @click="insertTag(rows)"
class="cursor-pointer" class="cursor-pointer"
:disable="!validRow" :disable="!validRow"
color="primary" color="primary"
name="add" flat
size="sm" icon="add"
shortcut="+"
style="flex: 0" style="flex: 0"
> >
<QTooltip> <QTooltip>
{{ t('itemTags.addTag') }} {{ t('itemTags.addTag') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</VnRow> </VnRow>
</QCard> </QCard>
</template> </template>

View File

@ -36,23 +36,23 @@ const user = state.getUser();
const fixedPrices = ref([]); const fixedPrices = ref([]);
const warehousesOptions = ref([]); const warehousesOptions = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
jsegarra marked this conversation as resolved Outdated

💣🤌

💣🤌
const itemFixedPriceFilterRef = ref(); const itemFixedPriceFilterRef = ref();
const params = reactive({}); const params = reactive({});
const defaultColumnAttrs = {
align: 'left',
sortable: true,
};
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
params.warehouseFk = user.value.warehouseFk; params.warehouseFk = user.value.warehouseFk;
}); });
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
const defaultColumnAttrs = {
align: 'left',
sortable: true,
};
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('item.fixedPrice.itemId'), label: t('item.fixedPrice.itemFk'),
name: 'itemId', name: 'itemFk',
...defaultColumnAttrs, ...defaultColumnAttrs,
isId: true, isId: true,
cardVisible: true, cardVisible: true,
@ -426,7 +426,7 @@ function handleOnDataSave({ CrudModelRef }) {
:default-save="false" :default-save="false"
data-key="ItemFixedPrices" data-key="ItemFixedPrices"
url="FixedPrices/filter" url="FixedPrices/filter"
:order="['itemFk ASC']" :order="['description DESC']"
save-url="FixedPrices/crud" save-url="FixedPrices/crud"
:user-params="{ warehouseFk: user.warehouseFk }" :user-params="{ warehouseFk: user.warehouseFk }"
ref="tableRef" ref="tableRef"
@ -460,7 +460,7 @@ function handleOnDataSave({ CrudModelRef }) {
<QCheckbox flat v-model="scope.selected" /> <QCheckbox flat v-model="scope.selected" />
</template> </template>
<template #column-itemId="props"> <template #column-itemFk="props">
<VnSelect <VnSelect
style="max-width: 100px" style="max-width: 100px"
url="Items/withName" url="Items/withName"
@ -514,7 +514,7 @@ function handleOnDataSave({ CrudModelRef }) {
</template> </template>
<template #column-minPrice="props"> <template #column-minPrice="props">
<QTd class="col"> <QTd class="col">
<div class="row"> <div class="row" style="align-items: center">
<QCheckbox <QCheckbox
:model-value="props.row.hasMinPrice" :model-value="props.row.hasMinPrice"
@update:model-value="updateMinPrice($event, props)" @update:model-value="updateMinPrice($event, props)"
@ -600,11 +600,14 @@ function handleOnDataSave({ CrudModelRef }) {
.q-table th, .q-table th,
.q-table td { .q-table td {
padding-inline: 5px !important; padding-inline: 5px !important;
// text-align: -webkit-right; }
.q-table tr td {
font-size: 10pt;
border-top: none;
border-collapse: collapse;
} }
.q-table tbody td { .q-table tbody td {
max-width: none; max-width: none;
.q-td.col { .q-td.col {
& .vnInputDate { & .vnInputDate {
min-width: 90px; min-width: 90px;

View File

@ -212,6 +212,7 @@ const decrement = (paramsObj, key) => {
flat flat
dense dense
size="12px" size="12px"
shortcut="+"
@click="add(params, 'scopeDays')" @click="add(params, 'scopeDays')"
/> />
<QBtn <QBtn

View File

@ -139,7 +139,11 @@ const columns = computed(() => [
</VnTable> </VnTable>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.full-width .vn-row > * { .full-width .vn-row {
flex: 0.4; margin-bottom: 0;
flex-direction: row;
> * {
max-width: 125px;
}
} }
</style> </style>

View File

@ -132,7 +132,6 @@ const openTab = (id) =>
:table="{ :table="{
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',
'hide-bottom': true,
}" }"
default-mode="table" default-mode="table"
:row-click="({ id }) => openTab(id)" :row-click="({ id }) => openTab(id)"

View File

@ -11,7 +11,7 @@ import FetchData from 'src/components/FetchData.vue';
import { dateRange } from 'src/filters'; import { dateRange } from 'src/filters';
defineProps({ dataKey: { type: String, required: true } }); defineProps({ dataKey: { type: String, required: true } });
const { t } = useI18n(); const { t, te } = useI18n();
const warehouses = ref(); const warehouses = ref();
const groupedStates = ref(); const groupedStates = ref();
@ -26,6 +26,12 @@ const handleScopeDays = (params, days, callback) => {
} }
if (callback) callback(); if (callback) callback();
}; };
const getLocale = (label) => {
const param = label.split('.').at(-1);
const globalLocale = `globals.params.${param}`;
return te(globalLocale) ? t(globalLocale) : t(`params.${param}`);
};
</script> </script>
<template> <template>
<FetchData url="Warehouses" auto-load @on-fetch="(data) => (warehouses = data)" /> <FetchData url="Warehouses" auto-load @on-fetch="(data) => (warehouses = data)" />
@ -43,10 +49,11 @@ const handleScopeDays = (params, days, callback) => {
:hidden-tags="['from', 'to', 'search']" :hidden-tags="['from', 'to', 'search']"
:custom-tags="['scopeDays']" :custom-tags="['scopeDays']"
:unremovable-params="['from', 'to', 'scopeDays']" :unremovable-params="['from', 'to', 'scopeDays']"
search-url="saleMonitorTickets"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
<strong v-text="`${t(`params.${tag.label}`)}:`" /> <strong v-text="`${getLocale(tag.label)}:`" />
<span v-text="formatFn(tag.value)" /> <span v-text="formatFn(tag.value)" />
</div> </div>
</template> </template>
@ -110,7 +117,7 @@ const handleScopeDays = (params, days, callback) => {
url="Workers/search" url="Workers/search"
:params="{ departmentCodes: ['VT'] }" :params="{ departmentCodes: ['VT'] }"
is-outlined is-outlined
option-value="code" option-value="id"
option-label="name" option-label="name"
:no-one="true" :no-one="true"
> >

View File

@ -54,7 +54,6 @@ const columns = computed(() => [
label: t('salesTicketsTable.problems'), label: t('salesTicketsTable.problems'),
name: 'totalProblems', name: 'totalProblems',
align: 'left', align: 'left',
columnFilter: false, columnFilter: false,
attrs: { attrs: {
dense: true, dense: true,
@ -65,7 +64,6 @@ const columns = computed(() => [
name: 'id', name: 'id',
field: 'id', field: 'id',
align: 'left', align: 'left',
columnFilter: { columnFilter: {
component: 'number', component: 'number',
name: 'id', name: 'id',
@ -108,9 +106,7 @@ const columns = computed(() => [
{ {
label: t('salesTicketsTable.date'), label: t('salesTicketsTable.date'),
name: 'shippedDate', name: 'shippedDate',
style: { 'max-width': '100px' },
align: 'left', align: 'left',
columnFilter: { columnFilter: {
component: 'date', component: 'date',
name: 'shippedDate', name: 'shippedDate',
@ -164,7 +160,6 @@ const columns = computed(() => [
label: t('salesTicketsTable.state'), label: t('salesTicketsTable.state'),
name: 'state', name: 'state',
align: 'left', align: 'left',
style: { 'max-width': '100px' },
columnFilter: { columnFilter: {
component: 'select', component: 'select',
name: 'stateFk', name: 'stateFk',
@ -193,7 +188,6 @@ const columns = computed(() => [
label: t('salesTicketsTable.zone'), label: t('salesTicketsTable.zone'),
name: 'zoneFk', name: 'zoneFk',
align: 'left', align: 'left',
columnFilter: { columnFilter: {
component: 'select', component: 'select',
name: 'zoneFk', name: 'zoneFk',
@ -210,8 +204,6 @@ const columns = computed(() => [
name: 'totalWithVat', name: 'totalWithVat',
field: 'totalWithVat', field: 'totalWithVat',
align: 'left', align: 'left',
style: { 'max-width': '75px' },
columnFilter: { columnFilter: {
component: 'number', component: 'number',
name: 'totalWithVat', name: 'totalWithVat',
@ -370,7 +362,7 @@ const openTab = (id) =>
</QCheckbox> </QCheckbox>
</template> </template>
<template #column-totalProblems="{ row }"> <template #column-totalProblems="{ row }">
<QTd class="no-padding" style="max-width: 60px"> <span>
<QIcon <QIcon
v-if="row.isTaxDataChecked === 0" v-if="row.isTaxDataChecked === 0"
name="vn:no036" name="vn:no036"
@ -424,46 +416,40 @@ const openTab = (id) =>
> >
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip> <QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
</QIcon> </QIcon>
</QTd> </span>
</template> </template>
<template #column-id="{ row }"> <template #column-id="{ row }">
<QTd class="no-padding"> <span class="link" @click.stop.prevent>
<span class="link" @click.stop.prevent> {{ row.id }}
{{ row.id }} <TicketDescriptorProxy :id="row.id" />
<TicketDescriptorProxy :id="row.id" /> </span>
</span>
</QTd>
</template> </template>
<template #column-clientFk="{ row }"> <template #column-clientFk="{ row }">
<QTd class="no-padding" @click.stop :title="row.nickname"> <div @click.stop :title="row.nickname">
<span class="link">{{ row.nickname }}</span> <span class="link" v-text="row.nickname" />
<CustomerDescriptorProxy :id="row.clientFk" /> <CustomerDescriptorProxy :id="row.clientFk" />
</QTd> </div>
</template> </template>
<template #column-salesPersonFk="{ row }"> <template #column-salesPersonFk="{ row }">
<QTd class="no-padding" @click.stop :title="row.userName"> <div @click.stop :title="row.userName">
<span class="link" v-text="dashIfEmpty(row.userName)" /> <span class="link" v-text="dashIfEmpty(row.userName)" />
<WorkerDescriptorProxy :id="row.salesPersonFk" /> <WorkerDescriptorProxy :id="row.salesPersonFk" />
</QTd> </div>
</template> </template>
<template #column-shippedDate="{ row }"> <template #column-shippedDate="{ row }">
<QTd class="no-padding"> <QBadge
<QBadge v-bind="getBadgeAttrs(row.shippedDate)"
v-bind="getBadgeAttrs(row.shippedDate)" class="q-pa-sm"
class="q-pa-sm" style="font-size: 14px"
style="font-size: 14px" >
> {{ formatShippedDate(row.shippedDate) }}
{{ formatShippedDate(row.shippedDate) }} </QBadge>
</QBadge>
</QTd>
</template> </template>
<template #column-provinceFk="{ row }"> <template #column-provinceFk="{ row }">
<QTd class="no-padding"> <span :title="row.province" v-text="row.province" />
<span :title="row.province" v-text="row.province" />
</QTd>
</template> </template>
<template #column-state="{ row }"> <template #column-state="{ row }">
<QTd class="no-padding" @click.stop.prevent> <div @click.stop.prevent>
<div v-if="row.refFk"> <div v-if="row.refFk">
<span class="link">{{ row.refFk }}</span> <span class="link">{{ row.refFk }}</span>
<InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> <InvoiceOutDescriptorProxy :id="row.invoiceOutId" />
@ -477,32 +463,33 @@ const openTab = (id) =>
> >
{{ row.state }} {{ row.state }}
</QBadge> </QBadge>
</QTd> </div>
</template> </template>
<template #column-isFragile="{ row }"> <template #column-isFragile="{ row }">
<QTd class="no-padding"> <QIcon v-if="row.isFragile" name="local_bar" color="primary" size="sm">
<QIcon v-if="row.isFragile" name="local_bar" color="primary" size="sm"> <QTooltip>{{ $t('salesTicketsTable.isFragile') }}</QTooltip>
<QTooltip>{{ $t('salesTicketsTable.isFragile') }}</QTooltip> </QIcon>
</QIcon>
</QTd>
</template> </template>
<template #column-zoneFk="{ row }"> <template #column-zoneFk="{ row }">
<QTd class="no-padding" @click.stop.prevent :title="row.zoneName"> <div @click.stop.prevent :title="row.zoneName">
<span class="link">{{ row.zoneName }}</span> <span class="link">{{ row.zoneName }}</span>
<ZoneDescriptorProxy :id="row.zoneFk" /> <ZoneDescriptorProxy :id="row.zoneFk" />
</QTd> </div>
</template> </template>
<template #column-totalWithVat="{ row }"> <template #column-totalWithVat="{ row }">
<QTd class="no-padding"> <QBadge
<QBadge :color="totalPriceColor(row) || 'transparent'"
:color="totalPriceColor(row) || 'transparent'" :text-color="totalPriceColor(row) ? 'black' : 'white'"
:text-color="totalPriceColor(row) ? 'black' : 'white'" class="q-pa-sm"
class="q-pa-sm" style="font-size: 14px"
style="font-size: 14px" >
> {{ toCurrency(row.totalWithVat) }}
{{ toCurrency(row.totalWithVat) }} </QBadge>
</QBadge>
</QTd>
</template> </template>
</VnTable> </VnTable>
</template> </template>
<style lang="scss" scoped>
td .q-icon {
margin: 0 2px;
}
</style>

View File

@ -374,8 +374,10 @@ function addOrder(value, field, params) {
/> />
</QItem> </QItem>
<QItem class="q-mt-lg"> <QItem class="q-mt-lg">
<QIcon <QBtn
name="add_circle" icon="add_circle"
shortcut="+"
flat
class="filter-icon" class="filter-icon"
@click="tagValues.push({})" @click="tagValues.push({})"
/> />

View File

@ -249,7 +249,7 @@ watch(
@on-fetch="(data) => (orderSummary.vat = data)" @on-fetch="(data) => (orderSummary.vat = data)"
auto-load auto-load
/> />
<QDrawer side="right" :width="270" v-model="stateStore.rightDrawer"> <QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
<QCard <QCard
class="order-lines-summary q-pa-lg" class="order-lines-summary q-pa-lg"
v-if="orderSummary.vat && orderSummary.total" v-if="orderSummary.vat && orderSummary.total"

View File

@ -14,14 +14,15 @@ import OrderFilter from './Card/OrderFilter.vue';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const tableRef = ref(); const tableRef = ref();
const agencyList = ref([]); const agencyList = ref([]);
const addressesList = ref([]); const addressesList = ref([]);
const clientId = ref(); const route = useRoute();
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -169,6 +170,13 @@ const getDateColor = (date) => {
if (comparation == 0) return 'bg-warning'; if (comparation == 0) return 'bg-warning';
if (comparation < 0) return 'bg-success'; if (comparation < 0) return 'bg-success';
}; };
onMounted(() => {
if (!route.query.createForm) return;
const clientId = route.query.createForm;
const id = JSON.parse(clientId);
fetchClientAddress(id.clientFk, id);
});
</script> </script>
<template> <template>
<OrderSearchbar /> <OrderSearchbar />
@ -184,13 +192,14 @@ const getDateColor = (date) => {
:order="['landed DESC', 'clientFk ASC', 'id DESC']" :order="['landed DESC', 'clientFk ASC', 'id DESC']"
:create="{ :create="{
urlCreate: 'Orders/new', urlCreate: 'Orders/new',
title: 'Create Order', title: t('module.cerateOrder'),
onDataSaved: (url) => { onDataSaved: (url) => {
tableRef.redirect(url); tableRef.redirect(url);
}, },
formInitialData: { formInitialData: {
active: true, active: true,
addressId: null, addressId: null,
clientFk: null,
}, },
}" }"
:user-params="{ showEmpty: false }" :user-params="{ showEmpty: false }"
@ -221,7 +230,7 @@ const getDateColor = (date) => {
<VnSelect <VnSelect
url="Clients" url="Clients"
:include="{ relation: 'addresses' }" :include="{ relation: 'addresses' }"
v-model="clientId" v-model="data.clientFk"
:label="t('module.customer')" :label="t('module.customer')"
@update:model-value="(id) => fetchClientAddress(id, data)" @update:model-value="(id) => fetchClientAddress(id, data)"
/> />

View File

@ -10,6 +10,7 @@ module:
total: Total total: Total
salesPerson: Sales Person salesPerson: Sales Person
address: Address address: Address
cerateOrder: Create order
lines: lines:
item: Item item: Item
warehouse: Warehouse warehouse: Warehouse

View File

@ -10,6 +10,7 @@ module:
total: Total total: Total
salesPerson: Comercial salesPerson: Comercial
address: Dirección address: Dirección
cerateOrder: Crear cesta
lines: lines:
item: Artículo item: Artículo
warehouse: Almacén warehouse: Almacén

View File

@ -27,12 +27,15 @@ const columns = computed(() => [
condition: () => true, condition: () => true,
}, },
isId: true, isId: true,
columnFilter: false,
}, },
{ {
align: 'left', align: 'left',
label: t('globals.name'), label: t('globals.name'),
name: 'name', name: 'name',
isTitle: true, isTitle: true,
columnFilter: false,
columnClass: 'expand',
}, },
{ {
align: 'left', align: 'left',
@ -70,18 +73,33 @@ const columns = computed(() => [
data-key="AgencyList" data-key="AgencyList"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
/> />
<VnTable <div class="list-container">
ref="tableRef" <div class="list">
data-key="AgencyList" <VnTable
url="Agencies" data-key="AgencyList"
order="name" url="Agencies"
:columns="columns" order="name"
:right-search="false" :columns="columns"
:use-model="true" :right-search="false"
redirect="agency" :use-model="true"
default-mode="card" redirect="agency"
/> default-mode="card"
/>
</div>
</div>
</template> </template>
<style lang="scss" scoped>
.list {
display: flex;
flex-direction: column;
align-items: center;
width: 55%;
}
.list-container {
display: flex;
justify-content: center;
}
</style>
<i18n> <i18n>
es: es:
isOwn: Tiene propietario isOwn: Tiene propietario

View File

@ -88,7 +88,7 @@ async function deleteWorCenter(id) {
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[18, 18]"> <QPageSticky :offset="[18, 18]">
<QBtn @click.stop="dialog.show()" color="primary" fab icon="add"> <QBtn @click.stop="dialog.show()" color="primary" fab shortcut="+" icon="add">
<QDialog ref="dialog"> <QDialog ref="dialog">
<FormModelPopup <FormModelPopup
:title="t('Add work center')" :title="t('Add work center')"

View File

@ -167,8 +167,8 @@ const setTicketsRoute = async () => {
<QTd :props="props"> <QTd :props="props">
<span class="link"> <span class="link">
{{ props.value }} {{ props.value }}
<TicketDescriptorProxy :id="props?.row?.id" />
</span> </span>
<TicketDescriptorProxy :id="props?.row?.id" />
</QTd> </QTd>
</template> </template>
<template #body-cell-client="props"> <template #body-cell-client="props">

View File

@ -103,8 +103,8 @@ es:
Roadmap: Troncal Roadmap: Troncal
ETD date: Fecha ETD ETD date: Fecha ETD
ETD hour: Hora ETD ETD hour: Hora ETD
Tractor plate: Matrícula tractor Tractor plate: Matrícula tractora
Trailer plate: Matrícula trailer Trailer plate: Matrícula remolque
Carrier: Transportista Carrier: Transportista
Price: Precio Price: Precio
Driver name: Nombre del conductor Driver name: Nombre del conductor

View File

@ -164,8 +164,8 @@ en:
to: To to: To
es: es:
params: params:
tractorPlate: Matrícula del tractor tractorPlate: Matrícula tractora
trailerPlate: Matrícula del trailer trailerPlate: Matrícula remolque
supplierFk: Transportista supplierFk: Transportista
price: Precio price: Precio
driverName: Nombre del conductor driverName: Nombre del conductor
@ -174,8 +174,8 @@ es:
to: Hasta to: Hasta
From: Desde From: Desde
To: Hasta To: Hasta
Tractor Plate: Matrícula del tractor Tractor Plate: Matrícula tractora
Trailer Plate: Matrícula del trailer Trailer Plate: Matrícula remolque
Carrier: Transportista Carrier: Transportista
Price: Precio Price: Precio
Driver name: Nombre del conductor Driver name: Nombre del conductor

View File

@ -65,9 +65,10 @@ const updateDefaultStop = (data) => {
</div> </div>
</QCardSection> </QCardSection>
<QCardSection> <QCardSection>
<QIcon <QBtn
name="add" flat
size="sm" icon="add"
shortcut="+"
class="cursor-pointer" class="cursor-pointer"
color="primary" color="primary"
@click="roadmapStopsCrudRef.insert()" @click="roadmapStopsCrudRef.insert()"
@ -75,7 +76,7 @@ const updateDefaultStop = (data) => {
<QTooltip> <QTooltip>
{{ t('Add stop') }} {{ t('Add stop') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</QCardSection> </QCardSection>
</QCard> </QCard>
</template> </template>

View File

@ -149,8 +149,8 @@ const filter = {
<i18n> <i18n>
es: es:
Carrier: Transportista Carrier: Transportista
Tractor Plate: Matrícula tractor Tractor Plate: Matrícula tractora
Trailer Plate: Matrícula trailer Trailer Plate: Matrícula remolque
Phone: Teléfono Phone: Teléfono
Worker: Trabajador Worker: Trabajador
Observations: Observaciones Observations: Observaciones

View File

@ -0,0 +1,360 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useQuasar } from 'quasar';
import { toDate } from 'src/filters';
import { useRouter } from 'vue-router';
import { usePrintService } from 'src/composables/usePrintService';
import axios from 'axios';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnTable from 'components/VnTable/VnTable.vue';
const { openReport } = usePrintService();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const quasar = useQuasar();
const selectedRows = ref([]);
const tableRef = ref([]);
const confirmationDialog = ref(false);
const startingDate = ref(null);
const router = useRouter();
const routeFilter = {
include: [
{
relation: 'workers',
scope: {
fields: ['id', 'firstName'],
},
},
],
};
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: 'Id',
chip: {
condition: () => true,
},
isId: true,
columnFilter: false,
},
{
align: 'left',
name: 'workerFk',
label: t('route.Worker'),
create: true,
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
useLike: false,
optionFilter: 'firstName',
find: {
value: 'workerFk',
label: 'workerUserName',
},
},
columnFilter: {
inWhere: true,
},
useLike: false,
cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
},
{
align: 'left',
name: 'agencyModeFk',
label: t('route.Agency'),
isTitle: true,
cardVisible: true,
create: true,
component: 'select',
attrs: {
url: 'agencyModes',
fields: ['id', 'name'],
find: {
value: 'agencyModeFk',
label: 'agencyName',
},
},
columnClass: 'expand',
},
{
align: 'left',
name: 'vehicleFk',
label: t('route.Vehicle'),
cardVisible: true,
create: true,
component: 'select',
attrs: {
url: 'vehicles',
fields: ['id', 'numberPlate'],
optionLabel: 'numberPlate',
optionFilterValue: 'numberPlate',
find: {
value: 'vehicleFk',
label: 'vehiclePlateNumber',
},
},
columnFilter: {
inWhere: true,
},
},
{
align: 'left',
name: 'created',
label: t('route.Date'),
columnFilter: false,
cardVisible: true,
create: true,
component: 'date',
format: ({ date }) => toDate(date),
},
{
align: 'left',
name: 'from',
label: t('route.From'),
visible: false,
cardVisible: true,
create: true,
component: 'date',
format: ({ date }) => toDate(date),
},
{
align: 'left',
name: 'to',
label: t('route.To'),
visible: false,
cardVisible: true,
create: true,
component: 'date',
format: ({ date }) => toDate(date),
},
{
align: 'center',
name: 'm3',
label: 'm3',
cardVisible: true,
columnClass: 'shrink',
},
{
align: 'left',
name: 'started',
label: t('route.hourStarted'),
component: 'time',
columnFilter: false,
},
{
align: 'left',
name: 'finished',
label: t('route.hourFinished'),
component: 'time',
columnFilter: false,
},
{
align: 'center',
name: 'kmStart',
label: t('route.KmStart'),
columnClass: 'shrink',
create: true,
visible: false,
},
{
align: 'center',
name: 'kmEnd',
label: t('route.KmEnd'),
columnClass: 'shrink',
create: true,
visible: false,
},
{
align: 'left',
name: 'description',
label: t('route.Description'),
isTitle: true,
create: true,
component: 'input',
field: 'description',
},
{
align: 'left',
name: 'isOk',
label: t('route.Served'),
component: 'checkbox',
columnFilter: false,
columnClass: 'shrink',
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('route.Add tickets'),
icon: 'vn:ticketAdd',
action: (row) => openTicketsDialog(row?.id),
isPrimary: true,
},
{
title: t('route.components.smartCard.viewSummary'),
icon: 'preview',
action: (row) => viewSummary(row?.id, RouteSummary),
isPrimary: true,
},
{
title: t('route.Route summary'),
icon: 'arrow_forward',
action: (row) => navigate(row?.id),
isPrimary: true,
},
],
},
]);
function navigate(id) {
router.push({ path: `/route/${id}` });
}
const cloneRoutes = () => {
if (!selectedRows.value.length || !startingDate.value) return;
axios.post('Routes/clone', {
created: startingDate.value,
ids: selectedRows.value.map((row) => row?.id),
});
startingDate.value = null;
tableRef.value.reload();
};
const showRouteReport = () => {
const ids = selectedRows.value.map((row) => row?.id);
const idString = ids.join(',');
let url = `Routes/${idString}/driver-route-pdf`;
let params = {};
if (selectedRows.value.length >= 1) {
params = {
id: idString,
};
url = `Routes/downloadZip`;
}
openReport(url, params, '_blank');
};
function markAsServed() {
selectedRows.value.forEach(async (row) => {
await axios.patch(`Routes/${row?.id}`, { isOk: true });
});
tableRef.value.reload();
startingDate.value = null;
}
const openTicketsDialog = (id) => {
quasar
.dialog({
component: RouteListTicketsDialog,
componentProps: {
id,
},
})
.onOk(() => tableRef.value.reload());
};
</script>
<template>
<RouteSearchbar />
<QDialog v-model="confirmationDialog">
<QCard style="min-width: 350px">
<QCardSection>
<p class="text-h6 q-ma-none">{{ t('route.Select the starting date') }}</p>
</QCardSection>
<QCardSection class="q-pt-none">
<VnInputDate
:label="t('route.Stating date')"
v-model="startingDate"
autofocus
/>
</QCardSection>
<QCardActions align="right">
<QBtn
flat
:label="t('route.Cancel')"
v-close-popup
class="text-primary"
/>
<QBtn color="primary" v-close-popup @click="cloneRoutes">
{{ t('globals.clone') }}
</QBtn>
</QCardActions>
</QCard>
</QDialog>
<VnSubToolbar />
<RightMenu>
<template #right-panel>
<RouteFilter data-key="RouteList" />
</template>
</RightMenu>
<VnTable
class="route-list"
ref="tableRef"
data-key="RouteList"
url="Routes/filter"
:columns="columns"
:right-search="false"
:is-editable="true"
:filter="routeFilter"
redirect="route"
:row-click="false"
:create="{
urlCreate: 'Routes',
title: t('route.createRoute'),
onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {},
}"
save-url="Routes/crud"
:disable-option="{ card: true }"
table-height="85vh"
v-model:selected="selectedRows"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
>
<template #moreBeforeActions>
<QBtn
icon="vn:clone"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="confirmationDialog = true"
>
<QTooltip>{{ t('route.Clone Selected Routes') }}</QTooltip>
</QBtn>
<QBtn
icon="cloud_download"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="showRouteReport"
>
<QTooltip>{{ t('route.Download selected routes as PDF') }}</QTooltip>
</QBtn>
<QBtn
icon="check"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="markAsServed()"
>
<QTooltip>{{ t('route.Mark as served') }}</QTooltip>
</QBtn>
</template>
</VnTable>
</template>

View File

@ -1,34 +1,19 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useQuasar } from 'quasar'; import { toHour } from 'src/filters';
import { toDate } from 'src/filters';
import { useRouter } from 'vue-router';
import axios from 'axios';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue'; import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue'; import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue'; import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { usePrintService } from 'src/composables/usePrintService'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
const { openReport } = usePrintService();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const quasar = useQuasar();
const session = useSession();
const selectedRows = ref([]);
const tableRef = ref([]); const tableRef = ref([]);
const confirmationDialog = ref(false);
const startingDate = ref(null);
const router = useRouter();
const routeFilter = { const routeFilter = {
include: [ include: [
{ {
@ -42,156 +27,70 @@ const routeFilter = {
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
isId: true,
name: 'id', name: 'id',
label: 'Id', label: 'Id',
chip: { chip: {
condition: () => true, condition: () => true,
}, },
isId: true,
columnFilter: false, columnFilter: false,
}, },
{ {
align: 'left', align: 'left',
name: 'workerFk', name: 'workerFk',
label: t('Worker'), label: t('route.Worker'),
create: true, create: true,
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
useLike: false,
optionFilter: 'firstName',
find: {
value: 'workerFk',
label: 'workerUserName',
},
},
columnFilter: {
inWhere: true,
},
useLike: false,
cardVisible: true, cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
},
{
align: 'left',
name: 'agencyModeFk',
label: t('Agency'),
isTitle: true,
cardVisible: true,
create: true,
component: 'select',
attrs: {
url: 'agencyModes',
fields: ['id', 'name'],
find: {
value: 'agencyModeFk',
label: 'agencyName',
},
},
columnClass: 'expand',
},
{
align: 'left',
name: 'vehicleFk',
label: t('Vehicle'),
cardVisible: true,
create: true,
component: 'select',
attrs: {
url: 'vehicles',
fields: ['id', 'numberPlate'],
optionLabel: 'numberPlate',
optionFilterValue: 'numberPlate',
find: {
value: 'vehicleFk',
label: 'vehiclePlateNumber',
},
},
columnFilter: {
inWhere: true,
},
},
{
align: 'left',
name: 'created',
label: t('Date'),
columnFilter: false, columnFilter: false,
cardVisible: true,
create: true,
component: 'date',
format: ({ date }) => toDate(date),
}, },
{ {
align: 'left', align: 'left',
name: 'from', name: 'agencyName',
label: t('From'), label: t('route.Agency'),
visible: false,
cardVisible: true, cardVisible: true,
create: true, create: true,
component: 'date', columnClass: 'expand',
format: ({ date }) => toDate(date), columnFilter: false,
}, },
{ {
align: 'left', align: 'left',
name: 'to', name: 'vehiclePlateNumber',
label: t('To'), label: t('route.Vehicle'),
visible: false,
cardVisible: true, cardVisible: true,
create: true, create: true,
component: 'date', columnFilter: false,
format: ({ date }) => toDate(date),
},
{
align: 'center',
name: 'm3',
label: t('Volume'),
cardVisible: true,
columnClass: 'shrink',
}, },
{ {
align: 'left', align: 'left',
name: 'started', name: 'started',
label: t('hourStarted'), label: t('route.hourStarted'),
component: 'time', cardVisible: true,
columnFilter: false, columnFilter: false,
format: (row) => toHour(row.started),
}, },
{ {
align: 'left', align: 'left',
name: 'finished', name: 'finished',
label: t('hourFinished'), label: t('route.hourFinished'),
component: 'time', cardVisible: true,
columnFilter: false, columnFilter: false,
}, format: (row) => toHour(row.started),
{
align: 'center',
name: 'kmStart',
label: t('KmStart'),
columnClass: 'shrink',
create: true,
visible: false,
},
{
align: 'center',
name: 'kmEnd',
label: t('KmEnd'),
columnClass: 'shrink',
create: true,
visible: false,
}, },
{ {
align: 'left', align: 'left',
name: 'description', name: 'description',
label: t('Description'), label: t('route.Description'),
cardVisible: true,
isTitle: true, isTitle: true,
create: true, create: true,
component: 'input',
field: 'description', field: 'description',
columnFilter: false,
}, },
{ {
align: 'left', align: 'left',
name: 'isOk', name: 'isOk',
label: t('Served'), label: t('route.Served'),
component: 'checkbox', component: 'checkbox',
columnFilter: false, columnFilter: false,
columnClass: 'shrink', columnClass: 'shrink',
@ -200,212 +99,43 @@ const columns = computed(() => [
align: 'right', align: 'right',
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{
title: t('Add tickets'),
icon: 'vn:ticketAdd',
action: (row) => openTicketsDialog(row?.id),
},
{ {
title: t('components.smartCard.viewSummary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
action: (row) => viewSummary(row?.id, RouteSummary), action: (row) => viewSummary(row?.id, RouteSummary),
},
{
title: t('Route summary'),
icon: 'arrow_forward',
isPrimary: true, isPrimary: true,
action: (row) => navigate(row?.id),
}, },
], ],
}, },
]); ]);
function navigate(id) {
router.push({ path: `/route/${id}` });
}
const cloneRoutes = () => {
if (!selectedRows.value.length || !startingDate.value) return;
axios.post('Routes/clone', {
created: startingDate.value,
ids: selectedRows.value.map((row) => row?.id),
});
startingDate.value = null;
tableRef.value.reload();
};
const showRouteReport = () => {
const ids = selectedRows.value.map((row) => row?.id);
const idString = ids.join(',');
let url = `Routes/${idString}/driver-route-pdf`;
let params = {};
if (selectedRows.value.length >= 1) {
params = {
id: idString,
};
url = `Routes/downloadZip`;
}
openReport(url, params, '_blank');
};
function markAsServed() {
selectedRows.value.forEach(async (row) => {
await axios.patch(`Routes/${row?.id}`, { isOk: true });
});
tableRef.value.reload();
startingDate.value = null;
}
const openTicketsDialog = (id) => {
quasar
.dialog({
component: RouteListTicketsDialog,
componentProps: {
id,
},
})
.onOk(() => tableRef.value.reload());
};
</script> </script>
<template> <template>
<RouteSearchbar /> <RouteSearchbar />
<QDialog v-model="confirmationDialog">
<QCard style="min-width: 350px">
<QCardSection>
<p class="text-h6 q-ma-none">{{ t('Select the starting date') }}</p>
</QCardSection>
<QCardSection class="q-pt-none">
<VnInputDate
:label="t('Stating date')"
v-model="startingDate"
autofocus
/>
</QCardSection>
<QCardActions align="right">
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
<QBtn color="primary" v-close-popup @click="cloneRoutes">
{{ t('globals.clone') }}
</QBtn>
</QCardActions>
</QCard>
</QDialog>
<VnSubToolbar />
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
<RouteFilter data-key="RouteList" /> <RouteFilter data-key="RouteList" />
</template> </template>
</RightMenu> </RightMenu>
<VnTable <VnTable
class="route-list"
ref="tableRef"
data-key="RouteList" data-key="RouteList"
url="Routes/filter" url="Routes/filter"
:columns="columns" :columns="columns"
:right-search="false" :right-search="false"
:is-editable="true"
:filter="routeFilter" :filter="routeFilter"
redirect="route" redirect="route"
:row-click="false"
:create="{ :create="{
urlCreate: 'Routes', urlCreate: 'Routes',
title: t('Create route'), title: t('route.createRoute'),
onDataSaved: ({ id }) => tableRef.redirect(id), onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {}, formInitialData: {},
}" }"
save-url="Routes/crud"
:disable-option="{ card: true }"
table-height="85vh" table-height="85vh"
v-model:selected="selectedRows"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
> >
<template #moreBeforeActions> <template #column-workerFk="{ row }">
<QBtn <span class="link" @click.stop>
icon="vn:clone" {{ row?.workerUserName }}
color="primary" <WorkerDescriptorProxy :id="row?.workerFk" v-if="row?.workerFk" />
class="q-mr-sm" </span>
:disable="!selectedRows?.length"
@click="confirmationDialog = true"
>
<QTooltip>{{ t('Clone Selected Routes') }}</QTooltip>
</QBtn>
<QBtn
icon="cloud_download"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="showRouteReport"
>
<QTooltip>{{ t('Download selected routes as PDF') }}</QTooltip>
</QBtn>
<QBtn
icon="check"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="markAsServed()"
>
<QTooltip>{{ t('Mark as served') }}</QTooltip>
</QBtn>
</template> </template>
</VnTable> </VnTable>
</template> </template>
<style lang="scss" scoped>
.table-input-cell {
max-width: 143px;
}
.route-list {
width: 100%;
max-height: 100%;
}
.table-actions {
gap: 12px;
}
th:last-child,
td:last-child {
background-color: var(--vn-section-color);
position: sticky;
right: 0;
}
</style>
<i18n>
en:
newRoute: New Route
hourStarted: Started hour
hourFinished: Finished hour
es:
From: Desde
To: Hasta
Worker: Trabajador
Agency: Agencia
Vehicle: Vehículo
Volume: Volumen
Date: Fecha
Description: Descripción
Hour started: Hora inicio
Hour finished: Hora fin
KmStart: Km inicio
KmEnd: Km fin
Served: Servida
newRoute: Nueva Ruta
Clone Selected Routes: Clonar rutas seleccionadas
Select the starting date: Seleccione la fecha de inicio
Stating date: Fecha de inicio
Cancel: Cancelar
Mark as served: Marcar como servidas
Download selected routes as PDF: Descargar rutas seleccionadas como PDF
Add ticket: Añadir tickets
Preview: Vista previa
Summary: Resumen
Route is closed: La ruta está cerrada
Route is not served: La ruta no está servida
hourStarted: Hora de inicio
hourFinished: Hora de fin
</i18n>

View File

@ -87,7 +87,7 @@ const columns = computed(() => [
actions: [ actions: [
{ {
title: t('Ver cmr'), title: t('Ver cmr'),
icon: 'visibility', icon: 'preview',
isPrimary: true, isPrimary: true,
action: (row) => viewSummary(row?.id, RoadmapSummary), action: (row) => viewSummary(row?.id, RoadmapSummary),
}, },
@ -237,4 +237,5 @@ es:
Price: Precio Price: Precio
Observations: Observaciones Observations: Observaciones
Preview: Vista previa Preview: Vista previa
Select the estimated date of departure (ETD): Selecciona la fecha estimada de salida
</i18n> </i18n>

View File

@ -342,10 +342,7 @@ const openSmsDialog = async () => {
</template> </template>
<template #body-cell-city="{ value, row }"> <template #body-cell-city="{ value, row }">
<QTd auto-width> <QTd auto-width>
<span <span class="link" @click="goToBuscaman(row)">
class="text-primary cursor-pointer"
@click="goToBuscaman(row)"
>
{{ value }} {{ value }}
<QTooltip>{{ t('Open buscaman') }}</QTooltip> <QTooltip>{{ t('Open buscaman') }}</QTooltip>
</span> </span>
@ -353,7 +350,7 @@ const openSmsDialog = async () => {
</template> </template>
<template #body-cell-client="{ value, row }"> <template #body-cell-client="{ value, row }">
<QTd auto-width> <QTd auto-width>
<span class="text-primary cursor-pointer"> <span class="link">
{{ value }} {{ value }}
<CustomerDescriptorProxy :id="row?.clientFk" /> <CustomerDescriptorProxy :id="row?.clientFk" />
</span> </span>
@ -361,7 +358,7 @@ const openSmsDialog = async () => {
</template> </template>
<template #body-cell-ticket="{ value, row }"> <template #body-cell-ticket="{ value, row }">
<QTd auto-width class="text-center"> <QTd auto-width class="text-center">
<span class="text-primary cursor-pointer"> <span class="link">
{{ value }} {{ value }}
<TicketDescriptorProxy :id="row?.id" /> <TicketDescriptorProxy :id="row?.id" />
</span> </span>
@ -396,7 +393,7 @@ const openSmsDialog = async () => {
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[20, 20]"> <QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="openTicketsDialog"> <QBtn fab icon="add" shortcut="+" color="primary" @click="openTicketsDialog">
<QTooltip> <QTooltip>
{{ t('Add ticket') }} {{ t('Add ticket') }}
</QTooltip> </QTooltip>

View File

@ -0,0 +1,25 @@
route:
Worker: Worker
Agency: Agency
Vehicle: Vehicle
Description: Description
hourStarted: H.Start
hourFinished: H.End
createRoute: Create route
From: From
To: To
Date: Date
KmStart: Km start
KmEnd: Km end
Served: Served
Clone Selected Routes: Clone selected routes
Select the starting date: Select the starting date
Stating date: Starting date
Cancel: Cancel
Mark as served: Mark as served
Download selected routes as PDF: Download selected routes as PDF
Add ticket: Add ticket
Preview: Preview
Summary: Summary
Route is closed: Route is closed
Route is not served: Route is not served

View File

@ -0,0 +1,25 @@
route:
Worker: Trabajador
Agency: Agencia
Vehicle: Vehículo
Description: Descripción
hourStarted: H.Inicio
hourFinished: H.Fin
createRoute: Crear ruta
From: Desde
To: Hasta
Date: Fecha
KmStart: Km inicio
KmEnd: Km fin
Served: Servida
Clone Selected Routes: Clonar rutas seleccionadas
Select the starting date: Seleccione la fecha de inicio
Stating date: Fecha de inicio
Cancel: Cancelar
Mark as served: Marcar como servidas
Download selected routes as PDF: Descargar rutas seleccionadas como PDF
Add ticket: Añadir tickets
Preview: Vista previa
Summary: Resumen
Route is closed: La ruta está cerrada
Route is not served: La ruta no está servida

View File

@ -102,9 +102,7 @@ const setWireTransfer = async () => {
<VnInput :label="t('supplier.accounts.iban')" v-model="row.iban"> <VnInput :label="t('supplier.accounts.iban')" v-model="row.iban">
<template #append> <template #append>
<QIcon name="info" class="cursor-info"> <QIcon name="info" class="cursor-info">
<QTooltip>{{ <QTooltip>{{ t('components.iban_tooltip') }}</QTooltip>
t('components.iban_tooltip')
}}</QTooltip>
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>
@ -115,6 +113,7 @@ const setWireTransfer = async () => {
option-label="bic" option-label="bic"
option-value="id" option-value="id"
hide-selected hide-selected
:roles-allowed-to-create="['financial']"
> >
<template #form> <template #form>
<CreateBankEntityForm <CreateBankEntityForm
@ -165,9 +164,10 @@ const setWireTransfer = async () => {
</div> </div>
</VnRow> </VnRow>
<VnRow> <VnRow>
<QIcon <QBtn
name="add" flat
size="sm" icon="add"
shortcut="+"
class="cursor-pointer" class="cursor-pointer"
color="primary" color="primary"
@click="supplierAccountRef.insert()" @click="supplierAccountRef.insert()"
@ -175,7 +175,7 @@ const setWireTransfer = async () => {
<QTooltip> <QTooltip>
{{ t('Add account') }} {{ t('Add account') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</VnRow> </VnRow>
</QCard> </QCard>
</template> </template>

View File

@ -84,9 +84,10 @@ const insertRow = () => {
</VnRow> </VnRow>
</QCardSection> </QCardSection>
<VnRow> <VnRow>
<QIcon <QBtn
name="add" flat
size="sm" icon="add"
shortcut="+"
class="cursor-pointer" class="cursor-pointer"
color="primary" color="primary"
@click="insertRow()" @click="insertRow()"
@ -94,7 +95,7 @@ const insertRow = () => {
<QTooltip> <QTooltip>
{{ t('Add contact') }} {{ t('Add contact') }}
</QTooltip> </QTooltip>
</QIcon> </QBtn>
</VnRow> </VnRow>
</QCard> </QCard>
</template> </template>

View File

@ -109,7 +109,7 @@ const getEntryQueryParams = (supplier) => {
:subtitle="data.subtitle" :subtitle="data.subtitle"
:filter="filter" :filter="filter"
@on-fetch="setData" @on-fetch="setData"
data-key="supplier" data-key="supplierDescriptor"
:summary="$props.summary" :summary="$props.summary"
> >
<template #body="{ entity }"> <template #body="{ entity }">

View File

@ -75,6 +75,19 @@ const columns = computed(() => [
}, },
visible: false, visible: false,
}, },
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.country'),
name: 'country',
columnFilter: {
component: 'select',
name: 'countryFk',
attrs: {
url: 'countries',
fields: ['id', 'name'],
},
},
},
]); ]);
</script> </script>

View File

@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
@ -115,7 +114,7 @@ const totalNewPrice = computed(() => {
const totalDifference = computed(() => { const totalDifference = computed(() => {
return rows.value.reduce((acc, item) => acc + item.component?.difference || 0, 0); return rows.value.reduce((acc, item) => acc + item.component?.difference || 0, 0);
}); });
const showMovablecolumn = computed(() => (haveDifferences.value > 0 ? ['movable'] : [])); const showMovableColumn = computed(() => (haveDifferences.value > 0 ? ['movable'] : []));
const haveDifferences = computed(() => _ticketData.value.sale?.haveDifferences); const haveDifferences = computed(() => _ticketData.value.sale?.haveDifferences);
const ticketHaveNegatives = () => { const ticketHaveNegatives = () => {
let _haveNegatives = false; let _haveNegatives = false;
@ -145,85 +144,83 @@ onUnmounted(() => (stateStore.rightDrawer = false));
@on-fetch="(data) => (ticketUpdateActions = data)" @on-fetch="(data) => (ticketUpdateActions = data)"
auto-load auto-load
/> />
<RightMenu> <QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
<template #right-panel> <QCard
<QCard class="q-pa-md q-mb-md q-ma-md color-vn-text"
class="q-pa-md q-mb-md q-ma-md color-vn-text" bordered
bordered flat
flat style="border-color: black"
style="border-color: black" >
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('basicData.total') }}
</span>
</QCardSection>
<QCardSection class="column items-left" horizontal>
<span>
{{ t('basicData.price') }}:
{{ toCurrency(totalPrice) }}
</span>
</QCardSection>
<QCardSection class="column items-left" horizontal>
<span>
{{ t('basicData.newPrice') }}: {{ toCurrency(totalNewPrice) }}
</span>
</QCardSection>
<QCardSection class="column items-left" horizontal>
<span>
{{ t('basicData.difference') }}: {{ toCurrency(totalDifference) }}
</span>
</QCardSection>
</QCard>
<QCard
v-if="totalDifference"
class="q-pa-md q-mb-md q-ma-md color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('basicData.chargeDifference') }}
</span>
</QCardSection>
<QCardSection
v-for="(action, index) in ticketUpdateActions"
:key="index"
horizontal
> >
<QCardSection horizontal> <QRadio
<span class="text-weight-bold text-subtitle1 text-center full-width"> v-model="_ticketData.option"
{{ t('basicData.total') }} :val="action.code"
</span> :label="action.description"
</QCardSection> dense
<QCardSection class="column items-center" horizontal> />
<span> </QCardSection>
{{ t('basicData.price') }}: </QCard>
{{ toCurrency(totalPrice) }} <QCard
</span> v-if="haveNegatives"
</QCardSection> class="q-pa-md q-mb-md q-ma-md color-vn-text"
<QCardSection class="column items-center" horizontal> bordered
<span> flat
{{ t('basicData.newPrice') }}: {{ toCurrency(totalNewPrice) }} style="border-color: black"
</span> >
</QCardSection> <QCardSection horizontal class="flex row items-center">
<QCardSection class="column items-center" horizontal> <QCheckbox
<span> :label="t('basicData.withoutNegatives')"
{{ t('basicData.difference') }}: {{ toCurrency(totalDifference) }} v-model="_ticketData.withoutNegatives"
</span> :toggle-indeterminate="false"
</QCardSection> />
</QCard> <QIcon name="info" size="xs" class="q-ml-sm">
<QCard <QTooltip max-width="350px">
v-if="totalDifference" {{ t('basicData.withoutNegativesInfo') }}
class="q-pa-md q-mb-md q-ma-md color-vn-text" </QTooltip>
bordered </QIcon>
flat </QCardSection>
style="border-color: black" </QCard>
> </QDrawer>
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('basicData.chargeDifference') }}
</span>
</QCardSection>
<QCardSection
v-for="(action, index) in ticketUpdateActions"
:key="index"
horizontal
>
<QRadio
v-model="_ticketData.option"
:val="action.code"
:label="action.description"
dense
/>
</QCardSection>
</QCard>
<QCard
v-if="haveNegatives"
class="q-pa-md q-mb-md q-ma-md color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal class="flex row items-center">
<QCheckbox
:label="t('basicData.withoutNegatives')"
v-model="_ticketData.withoutNegatives"
:toggle-indeterminate="false"
/>
<QIcon name="info" size="xs" class="q-ml-sm">
<QTooltip max-width="350px">
{{ t('basicData.withoutNegativesInfo') }}
</QTooltip>
</QIcon>
</QCardSection>
</QCard>
</template>
</RightMenu>
<QTable <QTable
:visible-columns="showMovablecolumn" :visible-columns="showMovableColumn"
:rows="rows" :rows="rows"
:columns="columns" :columns="columns"
row-key="id" row-key="id"
@ -233,15 +230,15 @@ onUnmounted(() => (stateStore.rightDrawer = false));
flat flat
> >
<template #body-cell-item="{ row }"> <template #body-cell-item="{ row }">
<QTd> <QTd @click.stop class="link">
<QBtn flat color="primary"> <QBtn flat>
{{ row.itemFk }} {{ row.itemFk }}
<ItemDescriptorProxy :id="row.itemFk" /> <ItemDescriptorProxy :id="row.itemFk" />
</QBtn> </QBtn>
</QTd> </QTd>
</template> </template>
<template #body-cell-description="{ row }"> <template #body-cell-description="{ row }">
<QTd> <QTd style="display: contents">
<div class="column"> <div class="column">
<span>{{ row.item.name }}</span> <span>{{ row.item.name }}</span>
<span class="color-vn-label">{{ row.item.subName }}</span> <span class="color-vn-label">{{ row.item.subName }}</span>

View File

@ -12,6 +12,7 @@ import VnInputTime from 'components/common/VnInputTime.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useValidator } from 'src/composables/useValidator';
import { toTimeFormat } from 'filters/date.js'; import { toTimeFormat } from 'filters/date.js';
const $props = defineProps({ const $props = defineProps({
@ -23,7 +24,7 @@ const $props = defineProps({
}); });
const emit = defineEmits(['updateForm']); const emit = defineEmits(['updateForm']);
const { validate } = useValidator();
const { notify } = useNotify(); const { notify } = useNotify();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -51,18 +52,18 @@ const agencyByWarehouseFilter = computed(() => ({
}, },
})); }));
const zonesFilter = computed(() => ({ function zoneWhere() {
fields: ['id', 'name'], if (formData?.value?.agencyModeFk) {
order: 'name ASC', return formData.value?.agencyModeFk
where: formData.value?.agencyModeFk ? {
? { shipped: formData.value?.shipped,
shipped: formData.value?.shipped, addressFk: formData.value?.addressFk,
addressFk: formData.value?.addressFk, agencyModeFk: formData.value?.agencyModeFk,
agencyModeFk: formData.value?.agencyModeFk, warehouseFk: formData.value?.warehouseFk,
warehouseFk: formData.value?.warehouseFk, }
} : {};
: {}, }
})); }
const getLanded = async (params) => { const getLanded = async (params) => {
try { try {
@ -293,13 +294,6 @@ onMounted(() => onFormModelInit());
@on-fetch="(data) => (agenciesOptions = data)" @on-fetch="(data) => (agenciesOptions = data)"
auto-load auto-load
/> />
<FetchData
ref="zonesFetchRef"
url="Zones/includingExpired"
:filter="zonesFilter"
@on-fetch="(data) => (zonesOptions = data)"
auto-load
/>
<QForm> <QForm>
<VnRow> <VnRow>
<VnSelect <VnSelect
@ -313,6 +307,7 @@ onMounted(() => onFormModelInit());
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
:rules="validate('basicData.client')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -333,6 +328,7 @@ onMounted(() => onFormModelInit());
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
:rules="validate('basicData.warehouse')"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
@ -345,6 +341,7 @@ onMounted(() => onFormModelInit());
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
:rules="validate('basicData.address')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -392,6 +389,7 @@ onMounted(() => onFormModelInit());
:label="t('basicData.alias')" :label="t('basicData.alias')"
v-model="formData.nickname" v-model="formData.nickname"
:required="true" :required="true"
:rules="validate('basicData.alias')"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md no-wrap"> <VnRow class="row q-gutter-md q-mb-md no-wrap">
@ -404,6 +402,7 @@ onMounted(() => onFormModelInit());
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
:rules="validate('basicData.company')"
/> />
<VnSelect <VnSelect
:label="t('basicData.agency')" :label="t('basicData.agency')"
@ -414,17 +413,22 @@ onMounted(() => onFormModelInit());
hide-selected hide-selected
map-options map-options
@focus="agencyFetchRef.fetch()" @focus="agencyFetchRef.fetch()"
:rules="validate('basicData.agency')"
/> />
<VnSelect <VnSelect
:label="t('basicData.zone')" :label="t('basicData.zone')"
v-model="zoneId" v-model="zoneId"
option-value="id" option-value="id"
option-label="name" option-label="name"
:options="zonesOptions" url="Zones/includingExpired"
:fields="['id', 'name']"
sort-by="id"
:where="zoneWhere"
hide-selected hide-selected
map-options map-options
:required="true" :required="true"
@focus="zonesFetchRef.fetch()" @focus="zonesFetchRef.fetch()"
:rules="validate('basicData.zone')"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -444,16 +448,19 @@ onMounted(() => onFormModelInit());
:label="t('basicData.shipped')" :label="t('basicData.shipped')"
v-model="formData.shipped" v-model="formData.shipped"
:required="true" :required="true"
:rules="validate('basicData.shipped')"
/> />
<VnInputTime <VnInputTime
:label="t('basicData.shippedHour')" :label="t('basicData.shippedHour')"
v-model="formData.shipped" v-model="formData.shipped"
:required="true" :required="true"
:rules="validate('basicData.shippedHour')"
/> />
<VnInputDate <VnInputDate
:label="t('basicData.landed')" :label="t('basicData.landed')"
v-model="formData.landed" v-model="formData.landed"
:required="true" :required="true"
:rules="validate('basicData.landed')"
/> />
</VnRow> </VnRow>
</QForm> </QForm>

View File

@ -3,7 +3,7 @@ import { ref, onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import BasicDataTable from './BasicDataTable.vue'; import TicketBasicData from './TicketBasicData.vue';
import TicketBasicDataForm from './TicketBasicDataForm.vue'; import TicketBasicDataForm from './TicketBasicDataForm.vue';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
@ -158,7 +158,10 @@ onBeforeMount(async () => await getTicketData());
color="primary" color="primary"
animated animated
keep-alive keep-alive
style="max-width: 800px; margin: auto" style="margin: auto"
:style="{
'max-width': step > 1 ? 'none' : '800px',
}"
> >
<QStep :name="1" :title="t('globals.pageTitles.basicData')" :done="step > 1"> <QStep :name="1" :title="t('globals.pageTitles.basicData')" :done="step > 1">
<TicketBasicDataForm <TicketBasicDataForm
@ -168,7 +171,7 @@ onBeforeMount(async () => await getTicketData());
/> />
</QStep> </QStep>
<QStep :name="2" :title="t('basicData.priceDifference')"> <QStep :name="2" :title="t('basicData.priceDifference')">
<BasicDataTable <TicketBasicData
:form-data="formData" :form-data="formData"
v-model:haveNegatives="haveNegatives" v-model:haveNegatives="haveNegatives"
@update-form="($event) => (formData = $event)" @update-form="($event) => (formData = $event)"

View File

@ -31,6 +31,7 @@ const router = useRouter();
const { notify } = useNotify(); const { notify } = useNotify();
const newTicketFormData = reactive({}); const newTicketFormData = reactive({});
const date = new Date();
const createTicket = async () => { const createTicket = async () => {
try { try {
@ -64,7 +65,11 @@ const createTicket = async () => {
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<VnRow> <VnRow>
<VnInputDate :label="t('expedition.landed')" v-model="data.landed" /> <VnInputDate
:label="t('expedition.landed')"
v-model="data.landed"
:model-value="date"
/>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput

View File

@ -5,15 +5,16 @@ import { useRoute } from 'vue-router';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
import VnImg from 'src/components/ui/VnImg.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import axios from 'axios'; import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -75,22 +76,39 @@ const columns = computed(() => [
name: 'item', name: 'item',
align: 'left', align: 'left',
}, },
{
align: 'left',
label: t('lines.image'),
name: 'image',
columnField: {
component: VnImg,
attrs: (id) => {
return {
id,
width: '50px',
};
},
},
columnFilter: false,
},
{ {
label: t('ticketComponents.description'), label: t('ticketComponents.description'),
name: 'description', name: 'description',
align: 'left', align: 'left',
columnClass: 'expand',
}, },
{ {
label: t('ticketComponents.quantity'), label: t('ticketComponents.quantity'),
name: 'quantity', name: 'quantity',
field: 'quantity', field: 'quantity',
align: 'left', align: 'left',
format: (val) => dashIfEmpty(val), format: (row) => dashIfEmpty(row.quantity),
}, },
{ {
label: t('ticketComponents.serie'), label: t('ticketComponents.serie'),
name: 'serie', name: 'serie',
align: 'left', align: 'left',
format: (row) => dashIfEmpty(row.serie),
}, },
{ {
label: t('ticketComponents.components'), label: t('ticketComponents.components'),
@ -174,181 +192,166 @@ onUnmounted(() => (stateStore.rightDrawer = false));
@on-fetch="(data) => (components = data)" @on-fetch="(data) => (components = data)"
auto-load auto-load
/> />
<RightMenu> <QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
<template #right-panel> <QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black">
<QCard <QCardSection horizontal>
class="q-pa-sm color-vn-text" <span class="text-weight-bold text-subtitle1 text-center full-width">
bordered {{ t('ticketComponents.total') }}
flat </span>
style="border-color: black" </QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label"
>{{ t('ticketComponents.baseToCommission') }}:
</span>
<span>{{ toCurrency(getBase) }}</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label"
>{{ t('ticketComponents.totalWithoutVat') }}:
</span>
<span>{{ toCurrency(getTotal) }}</span>
</QCardSection>
</QCard>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black">
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.components') }}
</span>
</QCardSection>
<QCardSection
v-for="(component, index) in componentsList"
:key="index"
horizontal
> >
<QCardSection horizontal> <span v-if="component.name" class="q-mr-xs color-vn-label">
<span class="text-weight-bold text-subtitle1 text-center full-width"> {{ component.name }}:
{{ t('ticketComponents.total') }} </span>
</span> <span v-if="component.value">{{
</QCardSection> toCurrency(component.value, 'EUR', 3)
<QCardSection horizontal> }}</span>
<span class="q-mr-xs color-vn-label" </QCardSection>
>{{ t('ticketComponents.baseToCommission') }}: </QCard>
</span> <QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black">
<span>{{ toCurrency(getBase) }}</span> <QCardSection horizontal>
</QCardSection> <span class="text-weight-bold text-subtitle1 text-center full-width">
<QCardSection horizontal> {{ t('ticketComponents.zoneBreakdown') }}
<span class="q-mr-xs color-vn-label" </span>
>{{ t('ticketComponents.totalWithoutVat') }}: </QCardSection>
</span> <QCardSection horizontal>
<span>{{ toCurrency(getTotal) }}</span> <span class="q-mr-xs color-vn-label">
</QCardSection> {{ t('ticketComponents.price') }}:
</QCard> </span>
<QCard <span>{{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }}</span>
class="q-pa-sm color-vn-text" </QCardSection>
bordered <QCardSection horizontal>
flat <span class="q-mr-xs color-vn-label">
style="border-color: black" {{ t('ticketComponents.bonus') }}:
> </span>
<QCardSection horizontal> <span>{{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }}</span>
<span class="text-weight-bold text-subtitle1 text-center full-width"> </QCardSection>
{{ t('ticketComponents.components') }} <QCardSection horizontal>
</span> <span class="q-mr-xs color-vn-label">
</QCardSection> {{ t('ticketComponents.zone') }}:
<QCardSection </span>
v-for="(component, index) in componentsList" <span class="link">
:key="index" {{ dashIfEmpty(ticketData?.zone?.name) }}
horizontal <ZoneDescriptorProxy :id="ticketData?.zone?.id" />
> </span>
<span v-if="component.name" class="q-mr-xs color-vn-label"> </QCardSection>
{{ component.name }}: <QCardSection v-if="ticketData?.zone?.isVolumetric" horizontal>
</span> <span class="q-mr-xs color-vn-label">
<span v-if="component.value">{{ {{ t('ticketComponents.volume') }}:
toCurrency(component.value, 'EUR', 3) </span>
}}</span> <span>{{ ticketVolume }}</span>
</QCardSection> </QCardSection>
</QCard> <QCardSection horizontal>
<QCard <span class="q-mr-xs color-vn-label">
class="q-pa-sm color-vn-text" {{ t('ticketComponents.packages') }}:
bordered </span>
flat <span>{{ dashIfEmpty(ticketData?.packages) }}</span>
style="border-color: black" </QCardSection>
> </QCard>
<QCardSection horizontal> <QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black">
<span class="text-weight-bold text-subtitle1 text-center full-width"> <QCardSection horizontal>
{{ t('ticketComponents.zoneBreakdown') }} <span class="text-weight-bold text-subtitle1 text-center full-width">
</span> {{ t('ticketComponents.theoricalCost') }}
</QCardSection> </span>
<QCardSection horizontal> </QCardSection>
<span class="q-mr-xs color-vn-label"> <QCardSection horizontal>
{{ t('ticketComponents.price') }}: <span class="q-mr-xs color-vn-label">
</span> {{ t('ticketComponents.totalPrice') }}:
<span>{{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }}</span> </span>
</QCardSection> <span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span>
<QCardSection horizontal> </QCardSection>
<span class="q-mr-xs color-vn-label"> </QCard>
{{ t('ticketComponents.bonus') }}: </QDrawer>
</span> <VnTable
<span>{{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }}</span> ref="tableRef"
</QCardSection> data-key="TicketComponents"
<QCardSection horizontal> url="Sales"
<span class="q-mr-xs color-vn-label"> :user-filter="salesFilter"
{{ t('ticketComponents.zone') }}:
</span>
<span class="link">
{{ dashIfEmpty(ticketData?.zone?.name) }}
<ZoneDescriptorProxy :id="ticketData?.zone?.id" />
</span>
</QCardSection>
<QCardSection v-if="ticketData?.zone?.isVolumetric" horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketComponents.volume') }}:
</span>
<span>{{ ticketVolume }}</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketComponents.packages') }}:
</span>
<span>{{ dashIfEmpty(ticketData?.packages) }}</span>
</QCardSection>
</QCard>
<QCard
class="q-pa-sm color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.theoricalCost') }}
</span>
</QCardSection>
<QCardSection horizontal>
<span class="q-mr-xs color-vn-label">
{{ t('ticketComponents.totalPrice') }}:
</span>
<span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span>
</QCardSection>
</QCard>
</template>
</RightMenu>
<QTable
:rows="components"
:columns="columns" :columns="columns"
row-key="id" :right-search="false"
:pagination="{ rowsPerPage: 0 }" auto-load
class="full-width q-mt-md" :disable-option="{ card: true }"
:no-data-label="t('globals.noResults')" :column-search="false"
> >
<template #body-cell-item="{ row }"> <template #column-item="{ row }">
<QTd> <span @click.stop flat class="link">
<QBtn flat color="primary"> {{ row.itemFk }}
<span class="link">{{ row.itemFk }}</span> <ItemDescriptorProxy :id="row.itemFk" />
<ItemDescriptorProxy :id="row.itemFk" /> </span>
</QBtn>
</QTd>
</template> </template>
<template #body-cell-description="{ row }"> <template #column-image="{ row }">
<QTd> <div class="image-wrapper">
<div class="column"> <VnImg :id="parseInt(row?.item?.id)" class="rounded" />
<span>{{ row.item.name }}</span> </div>
<span class="color-vn-label">{{ row.item.subName }}</span>
<FetchedTags :item="row.item" />
</div>
</QTd>
</template> </template>
<template #body-cell-serie="{ row }"> <template #column-description="{ row }">
<QTd> <div class="column">
<div class="column"> <span>{{ row.item.name }}</span>
<span v-for="(saleComponent, index) in row.components" :key="index"> <span class="color-vn-label">{{ row.item.subName }}</span>
{{ saleComponent.component?.componentType?.name }} <FetchedTags :item="row.item" />
</span> </div>
</div>
</QTd>
</template> </template>
<template #body-cell-components="{ row }"> <template #column-serie="{ row }">
<QTd> <div class="column">
<div class="column"> <span v-for="(saleComponent, index) in row.components" :key="index">
<span v-for="(saleComponent, index) in row.components" :key="index"> {{ saleComponent.component?.componentType?.name }}
{{ saleComponent.component?.name }} </span>
</span> </div>
</div>
</QTd>
</template> </template>
<template #body-cell-import="{ row }"> <template #column-components="{ row }">
<QTd> <div class="column">
<div class="column text-right"> <span v-for="(saleComponent, index) in row.components" :key="index">
<span v-for="(saleComponent, index) in row.components" :key="index"> {{ saleComponent.component?.name }}
</span>
</div>
</template>
<template #column-import="{ row }">
<div class="column text-left">
<span v-for="(saleComponent, index) in row.components" :key="index">
{{ toCurrency(saleComponent.value, 'EUR', 3) }}
</span>
</div>
</template>
<template #column-total="{ row }">
<div class="column">
<span v-for="(saleComponent, index) in row.components" :key="index">
{{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }}
<!-- <QTooltip>
{{ saleComponent.component?.name }}:
{{ toCurrency(saleComponent.value, 'EUR', 3) }} {{ toCurrency(saleComponent.value, 'EUR', 3) }}
</span> </QTooltip> -->
</div> </span>
</QTd> </div>
</template> </template>
<template #body-cell-total="{ row }"> </VnTable>
<QTd>
<div class="column text-right">
<span v-for="(saleComponent, index) in row.components" :key="index">
{{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }}
</span>
</div>
</QTd>
</template>
</QTable>
</template> </template>
<style lang="scss" scoped>
.image-wrapper {
width: 40px;
height: 40px;
}
</style>

View File

@ -2,13 +2,13 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { toDateTimeFormat } from 'src/filters/date';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -30,13 +30,24 @@ const filter = {
{ {
relation: 'address', relation: 'address',
scope: { scope: {
fields: ['id', 'name', 'mobile', 'phone'], fields: ['id', 'name', 'mobile', 'phone', 'incotermsFk'],
}, },
}, },
{ {
relation: 'client', relation: 'client',
scope: { scope: {
fields: ['id', 'name', 'salesPersonFk', 'phone', 'mobile', 'email'], fields: [
'id',
'name',
'salesPersonFk',
'phone',
'mobile',
'email',
'isActive',
'isFreezed',
'isTaxDataChecked',
'hasElectronicInvoice',
],
include: [ include: [
{ {
relation: 'user', relation: 'user',
@ -87,6 +98,10 @@ const filter = {
}; };
const data = ref(useCardDescription()); const data = ref(useCardDescription());
function ticketFilter(ticket) {
return JSON.stringify({ clientFk: ticket.clientFk });
}
</script> </script>
<template> <template>
@ -128,7 +143,10 @@ const data = ref(useCardDescription());
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('ticket.card.shipped')" :value="toDate(entity.shipped)" /> <VnLv
:label="t('ticket.card.shipped')"
:value="toDateTimeFormat(entity.shipped)"
/>
<VnLv <VnLv
v-if="entity.agencyMode" v-if="entity.agencyMode"
:label="t('ticket.card.agency')" :label="t('ticket.card.agency')"
@ -138,7 +156,39 @@ const data = ref(useCardDescription());
<VnLv :label="t('ticket.card.alias')" :value="entity.nickname" /> <VnLv :label="t('ticket.card.alias')" :value="entity.nickname" />
</template> </template>
<template #icons="{ entity }"> <template #icons="{ entity }">
<QCardActions> <QCardActions class="q-gutter-x-xs">
<QIcon
v-if="entity.client.isActive == false"
name="vn:disabled"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client inactive') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.client.isFreezed == true"
name="vn:frozen"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client Frozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.problem.includes('hasRisk')"
name="vn:risk"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client has debt') }}</QTooltip>
</QIcon>
<QIcon
v-if="entity.client.isTaxDataChecked == false"
name="vn:no036"
size="xs"
color="primary"
>
<QTooltip>{{ t('Client not checked') }}</QTooltip>
</QIcon>
<QIcon <QIcon
v-if="entity.isDeleted == true" v-if="entity.isDeleted == true"
name="vn:deletedTicket" name="vn:deletedTicket"
@ -159,6 +209,27 @@ const data = ref(useCardDescription());
> >
<QTooltip>{{ t('ticket.card.customerCard') }}</QTooltip> <QTooltip>{{ t('ticket.card.customerCard') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
size="md"
icon="vn:ticket"
color="primary"
:to="{ name: 'TicketList', query: { table: ticketFilter(entity) } }"
>
<QTooltip>{{ t('ticket.card.ticketList') }}</QTooltip>
</QBtn>
<QBtn
size="md"
icon="vn:basketadd"
color="primary"
:to="{
name: 'OrderList',
query: {
createForm: JSON.stringify({ clientFk: entity.clientFk }),
},
}"
>
<QTooltip>{{ t('ticket.card.newOrder') }}</QTooltip>
</QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </CardDescriptor>
@ -168,4 +239,8 @@ const data = ref(useCardDescription());
es: es:
This ticket is deleted: Este ticket está eliminado This ticket is deleted: Este ticket está eliminado
Go to module index: Ir al índice del modulo Go to module index: Ir al índice del modulo
Client inactive: Cliente inactivo
Client not checked: Cliente no verificado
Client has debt: Cliente con deuda
Client Frozen: Cliente congelado
</i18n> </i18n>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { ref } from 'vue'; import { ref, toRefs } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -9,6 +9,11 @@ import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import VnSmsDialog from 'components/common/VnSmsDialog.vue'; import VnSmsDialog from 'components/common/VnSmsDialog.vue';
import toDate from 'filters/toDate'; import toDate from 'filters/toDate';
import FormPopup from 'components/FormPopup.vue';
import VnSelect from 'components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnInputTime from 'src/components/common/VnInputTime.vue';
import { useAcl } from 'src/composables/useAcl';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
@ -24,8 +29,16 @@ const { dialog, notify } = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const { openReport, sendEmail } = usePrintService(); const { openReport, sendEmail } = usePrintService();
const ticketSummary = useArrayData('TicketSummary'); const ticketSummary = useArrayData('TicketSummary');
const ticket = ref(props.ticket); const { ticket } = toRefs(props);
const ticketId = currentRoute.value.params.id; const ticketId = currentRoute.value.params.id;
const client = ref();
const showTransferDialog = ref(false);
const showTurnDialog = ref(false);
const showChangeTimeDialog = ref(false);
const dialogRef = ref();
const isEditable = ref();
const hasInvoicing = useAcl('invoicing');
const hasPdf = ref();
const weight = ref(); const weight = ref();
const actions = { const actions = {
clone: async () => { clone: async () => {
@ -76,6 +89,7 @@ const actions = {
notify({ notify({
message: t('You can undo this action within the first hour'), message: t('You can undo this action within the first hour'),
icon: 'info', icon: 'info',
type: 'warning',
}); });
push({ name: 'TicketList' }); push({ name: 'TicketList' });
@ -87,10 +101,14 @@ const actions = {
function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') { function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') {
const path = `Tickets/${ticket.value.id}/delivery-note-${documentType}`; const path = `Tickets/${ticket.value.id}/delivery-note-${documentType}`;
openReport(path, { openReport(
recipientId: ticket.value.clientFk, path,
type: type, {
}); recipientId: ticket.value.clientFk,
type: type,
},
'_blank'
);
} }
function sendDeliveryNoteConfirmation(type = 'deliveryNote', documentType = 'pdf') { function sendDeliveryNoteConfirmation(type = 'deliveryNote', documentType = 'pdf') {
@ -173,34 +191,280 @@ function openConfirmDialog(callback) {
dialog({ dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
title: t('This ticket will be removed from current route! Continue anyway?'),
message: t('You are going to delete this ticket'),
promise: actions[callback], promise: actions[callback],
}, },
}); });
} }
function generatePdfDialog() {
dialog({
component: VnConfirm,
componentProps: {
promise: generatePdfInvoice,
},
});
}
async function generatePdfInvoice() {
const { data } = await axios.get('invoiceOuts', {
params: {
filter: JSON.stringify({
where: { ref: ticket.value.refFk },
}),
},
});
const invoiceId = data[0].id;
const { response } = await axios.post(`InvoiceOuts/${invoiceId}/createPdf`);
if (!response) {
notify({
message: 'The invoice PDF document has been regenerated',
type: 'positive',
});
}
}
function makeInvoiceDialog() {
dialog({
component: VnConfirm,
componentProps: {
title: t('Are you sure you want to invoice this ticket?'),
message: t('You are going to invoice this ticket'),
promise: makeInvoice,
},
});
}
async function makeInvoice() {
const params = {
ticketsIds: [parseInt(ticketId)],
};
await axios.post(`Tickets/invoiceTicketsAndPdf`, params);
notify({
message: t('Ticket invoiced'),
type: 'positive',
});
window.location.reload();
}
async function transferClient(client) {
const params = {
clientFk: client,
};
const { data } = await axios.patch(`Tickets/${ticketId}/transferClient`, params);
if (data) window.location.reload();
}
async function addTurn(day) {
const params = {
ticketFk: parseInt(ticketId),
weekDay: day,
agencyModeFk: ticket.value.agencyModeFk,
};
await axios.patch(`TicketWeeklies`, params);
notify({
message: t('Current ticket deleted and added to shift'),
type: 'positive',
});
window.location.reload();
}
async function createRefund(withWarehouse) {
const params = {
ticketsIds: [parseInt(ticketId)],
withWarehouse: withWarehouse,
};
const { data } = await axios.post(`Tickets/refund`, params);
if (data) {
const refundTicket = data;
push({ name: 'TicketSale', params: { id: refundTicket[0].id } });
}
}
async function changeShippedHour(time) {
const params = {
shipped: time,
};
const { data } = await axios.post(`Tickets/${ticketId}/updateEditableTicket`, params);
if (data) window.location.reload();
}
function openRecalculateDialog() {
dialog({
component: VnConfirm,
componentProps: {
title: t('Recalculate components'),
message: t('Are you sure you want to recalculate components?'),
promise: recalculateComponents,
},
});
}
async function recalculateComponents() {
await axios.post(`Tickets/${ticketId}/recalculateComponents`);
notify({
message: t('Data saved'),
type: 'positive',
});
window.location.reload();
}
const handleFetchData = (data) => {
isEditable.value = data;
handleInvoiceOutData();
};
async function handleInvoiceOutData() {
const { data } = await axios.get(`InvoiceOuts`, {
params: {
filter: JSON.stringify({ where: { ref: ticket.value.refFk } }),
},
});
hasPdf.value = data[0]?.hasPdf;
}
</script> </script>
<template> <template>
<FetchData
:url="`Tickets/${ticketId}/isEditable`"
auto-load
@on-fetch="handleFetchData"
/>
<QItem @click="showTransferDialog = !showTransferDialog" v-ripple clickable>
<QItemSection avatar>
<QIcon name="content_paste" />
</QItemSection>
<QItemSection>{{ t('Transfer client') }}</QItemSection>
</QItem>
<QDialog ref="dialogRef" v-model="showTransferDialog">
<FormPopup
@on-submit="transferClient(client)"
:title="t('Transfer client')"
:custom-submit-button-label="t('Transfer client')"
:default-cancel-button="false"
>
<template #form-inputs>
<VnSelect
url="Clients"
:fields="['id', 'name']"
option-label="name"
option-value="id"
v-model="client"
:label="t('Client')"
auto-load
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ `#${scope.opt.id} - ` }}
{{ scope.opt.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</template>
</FormPopup>
</QDialog>
<QItem @click="showTurnDialog = !showTurnDialog" v-ripple clickable>
<QItemSection avatar>
<QIcon name="vn:calendar" />
</QItemSection>
<QItemSection>{{ t('addTurn') }}</QItemSection>
</QItem>
<QDialog ref="dialogRef" v-model="showTurnDialog">
<FormPopup
@on-submit="addTurn"
:title="t('What is the day of receipt of the ticket?')"
:default-submit-button="false"
:default-cancel-button="false"
style="text-align: center"
>
<template #form-inputs>
<QBtnGroup spread>
<QBtn
:label="t('weekdays.mon')"
color="primary"
@click="addTurn(0)"
v-ripple
class="weekdaysBtn"
/>
<QBtn
:label="t('weekdays.tue')"
color="primary"
@click="addTurn(1)"
v-ripple
class="weekdaysBtn"
/>
<QBtn
:label="t('weekdays.wed')"
color="primary"
@click="addTurn(2)"
v-ripple
class="weekdaysBtn"
/>
<QBtn
:label="t('weekdays.thu')"
color="primary"
@click="addTurn(3)"
v-ripple
class="weekdaysBtn"
/>
<QBtn
:label="t('weekdays.fri')"
color="primary"
@click="addTurn(4)"
v-ripple
class="weekdaysBtn"
/>
<QBtn
:label="t('weekdays.sat')"
color="primary"
@click="addTurn(5)"
v-ripple
class="weekdaysBtn"
/>
<QBtn
:label="t('weekdays.sun')"
color="primary"
@click="addTurn(6)"
v-ripple
class="weekdaysBtn"
/>
</QBtnGroup>
</template>
</FormPopup>
</QDialog>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection avatar> <QItemSection avatar>
<QIcon name="picture_as_pdf" /> <QIcon name="picture_as_pdf" />
</QItemSection> </QItemSection>
<QItemSection>{{ t('Open Delivery Note...') }}</QItemSection> <QItemSection>{{ t('Show Delivery Note...') }}</QItemSection>
<QItemSection side> <QItemSection side>
<QIcon name="keyboard_arrow_right" /> <QIcon name="keyboard_arrow_right" />
</QItemSection> </QItemSection>
<QMenu anchor="top end" self="top start" auto-close bordered> <QMenu anchor="top end" self="top start" auto-close bordered>
<QList> <QList>
<QItem @click="openDeliveryNote('deliveryNote')" v-ripple clickable> <QItem @click="openDeliveryNote('deliveryNote')" v-ripple clickable>
<QItemSection>{{ t('With prices') }}</QItemSection> <QItemSection>{{ t('as PDF') }}</QItemSection>
</QItem> </QItem>
<QItem @click="openDeliveryNote('withoutPrices')" v-ripple clickable> <QItem @click="openDeliveryNote('withoutPrices')" v-ripple clickable>
<QItemSection>{{ t('Without prices') }}</QItemSection> <QItemSection>{{ t('as PDF without prices') }}</QItemSection>
</QItem> </QItem>
<QItem <QItem
@click="openDeliveryNote('deliveryNote', 'csv')" @click="openDeliveryNote('deliveryNote', 'csv')"
v-ripple v-ripple
clickable clickable
> >
<QItemSection>{{ t('As CSV') }}</QItemSection> <QItemSection>{{ t('as CSV') }}</QItemSection>
</QItem> </QItem>
</QList> </QList>
</QMenu> </QMenu>
@ -220,21 +484,21 @@ function openConfirmDialog(callback) {
v-ripple v-ripple
clickable clickable
> >
<QItemSection>{{ t('With prices') }}</QItemSection> <QItemSection>{{ t('Send PDF') }}</QItemSection>
</QItem> </QItem>
<QItem <QItem
@click="sendDeliveryNoteConfirmation('withoutPrices')" @click="sendDeliveryNoteConfirmation('withoutPrices')"
v-ripple v-ripple
clickable clickable
> >
<QItemSection>{{ t('Without prices') }}</QItemSection> <QItemSection>{{ t('Send PDF to tablet') }}</QItemSection>
</QItem> </QItem>
<QItem <QItem
@click="sendDeliveryNoteConfirmation('deliveryNote', 'csv')" @click="sendDeliveryNoteConfirmation('deliveryNote', 'csv')"
v-ripple v-ripple
clickable clickable
> >
<QItemSection>{{ t('As CSV') }}</QItemSection> <QItemSection>{{ t('Send CSV') }}</QItemSection>
</QItem> </QItem>
</QList> </QList>
</QMenu> </QMenu>
@ -243,8 +507,26 @@ function openConfirmDialog(callback) {
<QItemSection avatar> <QItemSection avatar>
<QIcon name="receipt" /> <QIcon name="receipt" />
</QItemSection> </QItemSection>
<QItemSection>{{ t('Open Proforma Invoice') }}</QItemSection> <QItemSection>{{ t('Show Proforma') }}</QItemSection>
</QItem> </QItem>
<QItem
v-if="isEditable"
@click="showChangeTimeDialog = !showChangeTimeDialog"
v-ripple
clickable
>
<QItemSection avatar>
<QIcon name="schedule" />
</QItemSection>
<QItemSection>{{ t('Change shipped hour') }}</QItemSection>
</QItem>
<QDialog ref="dialogRef" v-model="showChangeTimeDialog">
<FormPopup @on-submit="changeShippedHour(time)" :title="t('Change shipped hour')">
<template #form-inputs>
<VnInputTime v-model="time" :label="t('Shipped hour')" clearable />
</template>
</FormPopup>
</QDialog>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection avatar> <QItemSection avatar>
<QIcon name="sms" /> <QIcon name="sms" />
@ -259,24 +541,72 @@ function openConfirmDialog(callback) {
<QItemSection>{{ t('Pending payment') }}</QItemSection> <QItemSection>{{ t('Pending payment') }}</QItemSection>
</QItem> </QItem>
<QItem @click="showSmsDialog('minAmount')" v-ripple clickable> <QItem @click="showSmsDialog('minAmount')" v-ripple clickable>
<QItemSection>{{ t('Minimum amount') }}</QItemSection> <QItemSection>{{ t('Minimum import') }}</QItemSection>
</QItem> </QItem>
<QItem <QItem
@click="showSmsDialogWithChanges('orderChanges')" @click="showSmsDialogWithChanges('orderChanges')"
v-ripple v-ripple
clickable clickable
> >
<QItemSection>{{ t('Order changes') }}</QItemSection> <QItemSection>{{ t('Notify changes') }}</QItemSection>
</QItem> </QItem>
</QList> </QList>
</QMenu> </QMenu>
</QItem> </QItem>
<QItem @click="makeInvoiceDialog()" v-ripple clickable v-if="isEditable">
<QItemSection avatar>
<QIcon name="picture_as_pdf" />
</QItemSection>
<QItemSection>{{ t('Make invoice') }}</QItemSection>
</QItem>
<QItem
@click="generatePdfDialog()"
v-ripple
clickable
v-if="ticket.refFk !== null && (hasInvoicing || hasPdf)"
>
<QItemSection avatar>
<QIcon name="picture_as_pdf" />
</QItemSection>
<QItemSection>{{
hasPdf ? t('Regenerate PDF invoice') : t('Generate PDF invoice')
}}</QItemSection>
</QItem>
<QItem @click="openConfirmDialog('clone')" v-ripple clickable> <QItem @click="openConfirmDialog('clone')" v-ripple clickable>
<QItemSection avatar> <QItemSection avatar>
<QIcon name="content_copy" /> <QIcon name="content_copy" />
</QItemSection> </QItemSection>
<QItemSection>{{ t('To clone ticket') }}</QItemSection> <QItemSection>{{ t('To clone ticket') }}</QItemSection>
</QItem> </QItem>
<QItem v-if="isEditable" @click="openRecalculateDialog()" v-ripple clickable>
<QItemSection avatar>
<QIcon name="refresh" />
</QItemSection>
<QItemSection>{{ t('Recalculate components') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection avatar>
<QIcon name="monetization_on" />
</QItemSection>
<QItemSection>{{ t('Refund all...') }}</QItemSection>
<QItemSection side>
<QIcon name="keyboard_arrow_right" />
</QItemSection>
<QMenu anchor="top end" self="top start" auto-close bordered>
<QList>
<QItem v-ripple clickable @click="createRefund(true)">
<QItemSection>
{{ t('with warehouse') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="createRefund(false)">
<QItemSection>
{{ t('without warehouse') }}
</QItemSection>
</QItem>
</QList>
</QMenu>
</QItem>
<QItem @click="$refs.weightDialog.show()" v-ripple clickable> <QItem @click="$refs.weightDialog.show()" v-ripple clickable>
<QItemSection avatar> <QItemSection avatar>
<QIcon name="weight" /> <QIcon name="weight" />
@ -307,29 +637,61 @@ function openConfirmDialog(callback) {
</template> </template>
</VnConfirm> </VnConfirm>
</template> </template>
<style lang="scss" scoped>
.weekdaysBtn {
margin: 1%;
}
</style>
<i18n> <i18n>
en: en:
addTurn: Add turn
invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}" invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}"
es: es:
Open Delivery Note...: Abrir albarán... Show Delivery Note...: Ver albarán...
Send Delivery Note...: Enviar albarán... Send Delivery Note...: Enviar albarán...
With prices: Con precios as PDF: como PDF
Without prices: Sin precios as PDF without prices: como PDF sin precios
As CSV: Como CSV as CSV: Como CSV
Open Proforma Invoice: Abrir factura proforma Send PDF: Enviar PDF
Send PDF to tablet: Enviar PDF a tablet
Send CSV: Enviar CSV
Show Proforma: Ver proforma
Delete ticket: Eliminar ticket Delete ticket: Eliminar ticket
Send SMS...: Enviar SMS Send SMS...: Enviar SMS...
Pending payment: Pago pendiente Pending payment: Pago pendiente
Minimum amount: Importe mínimo Minimum import: Importe mínimo
Order changes: Cambios del pedido Notify changes: Notificar cambios
Ticket deleted: Ticket eliminado Ticket deleted: Ticket eliminado
You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora
To clone ticket: Clonar ticket To clone ticket: Clonar ticket
Ticket cloned: Ticked clonado Ticket cloned: Ticked clonado
It was not able to clone the ticket: No se pudo clonar el ticket It was not able to clone the ticket: No se pudo clonar el ticket
Generate PDF invoice: Generar PDF factura
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
Transfer client: Transferir cliente
Client: Cliente
addTurn: Añadir a turno
What is the day of receipt of the ticket?: ¿Cuál es el día de preparación del pedido?
Current ticket deleted and added to shift: Ticket actual eliminado y añadido al turno
Refund all...: Abonar todo...
with warehouse: con almacén
without warehouse: sin almacén
Make invoice: Crear factura
Change shipped hour: Cambiar hora de envío
Shipped hour: Hora de envío
Recalculate components: Recalcular componentes
Are you sure you want to recalculate components?: ¿Seguro que quieres recalcular los componentes?
Data saved: Datos guardados
Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket?
You are going to invoice this ticket: Vas a facturar este ticket
Ticket invoiced: Ticket facturado
Set weight: Establecer peso Set weight: Establecer peso
Weight set: Peso establecido Weight set: Peso establecido
This ticket may be invoiced, do you want to continue?: Es posible que se facture este ticket, desea continuar? This ticket may be invoiced, do you want to continue?: Es posible que se facture este ticket, desea continuar?
invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}" invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}"
This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas?
You are going to delete this ticket: Vas a eliminar este ticket
</i18n> </i18n>

View File

@ -1,39 +1,35 @@
<script setup> <script setup>
import { onMounted, ref, computed, onUnmounted, reactive, watch } from 'vue'; import { onMounted, ref, computed, onUnmounted, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnInput from 'src/components/common/VnInput.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import TicketEditManaProxy from './TicketEditMana.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import ExpeditionNewTicket from './ExpeditionNewTicket.vue'; import ExpeditionNewTicket from './ExpeditionNewTicket.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toPercentage } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import axios from 'axios'; import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const editPriceProxyRef = ref(null);
const newTicketDialogRef = ref(null); const newTicketDialogRef = ref(null);
const logsTableDialogRef = ref(null); const logsTableDialogRef = ref(null);
const tableRef = ref();
const expeditionsLogsData = ref([]); const expeditionsLogsData = ref([]);
const selectedExpeditions = ref([]); const selectedExpeditions = ref([]);
const allColumnNames = ref([]); const allColumnNames = ref([]);
const visibleColumns = ref([]);
const newTicketWithRoute = ref(false); const newTicketWithRoute = ref(false);
const selectedRows = ref([]);
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
const exprBuilder = (param, value) => { const exprBuilder = (param, value) => {
switch (param) { switch (param) {
@ -54,8 +50,6 @@ const expeditionsArrayData = useArrayData('ticketExpeditions', {
filter: expeditionsFilter.value, filter: expeditionsFilter.value,
exprBuilder: exprBuilder, exprBuilder: exprBuilder,
}); });
const expeditionsStore = expeditionsArrayData.store;
const ticketExpeditions = computed(() => expeditionsStore.data);
const ticketArrayData = useArrayData('ticketData'); const ticketArrayData = useArrayData('ticketData');
const ticketStore = ticketArrayData.store; const ticketStore = ticketArrayData.store;
@ -73,129 +67,87 @@ watch(
{ immediate: true } { immediate: true }
); );
const params = reactive({});
const applyColumnFilter = async (col) => {
try {
const paramKey = col.columnFilter?.filterParamKey || col.field;
params[paramKey] = col.columnFilter.filterValue;
await expeditionsArrayData.addFilter({ filter: expeditionsFilter.value, params });
} catch (err) {
console.error('Error applying column filter', err);
}
};
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left',
label: t('expedition.id'), label: t('expedition.id'),
name: 'id', name: 'id',
field: 'id', chip: {
align: 'left', condition: () => true,
sortable: true, },
isId: true,
columnFilter: { columnFilter: {
component: VnInput, inWhere: true,
type: 'text',
filterParamKey: 'expeditionFk',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
}, },
{ {
label: t('expedition.item'), label: t('expedition.item'),
name: 'item', name: 'packagingItemFk',
align: 'left', align: 'left',
cardVisible: true,
columnFilter: { columnFilter: {
component: VnInput, inWhere: true,
type: 'text',
filterParamKey: 'packageItemName',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
}, },
{ {
label: t('expedition.name'), label: t('expedition.name'),
name: 'name', name: 'packageItemName',
field: 'packageItemName',
align: 'left', align: 'left',
isTitle: true,
columnFilter: { columnFilter: {
component: VnSelect, inWhere: true,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
url: 'Items',
fields: ['id', 'name'],
'sort-by': 'name ASC',
'option-value': 'id',
'option-label': 'name',
dense: true,
},
}, },
}, },
{ {
label: t('expedition.packageType'), label: t('expedition.packageType'),
name: 'packageType', name: 'freightItemName',
field: 'freightItemName',
align: 'left', align: 'left',
columnFilter: { columnFilter: {
component: VnInput, inWhere: true,
type: 'text',
// filterParamKey: 'expeditionFk',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
}, },
{ {
label: t('expedition.counter'), label: t('expedition.counter'),
name: 'counter', name: 'counter',
field: 'counter',
align: 'left', align: 'left',
columnFilter: null, columnFilter: {
inWhere: true,
},
}, },
{ {
label: t('expedition.externalId'), label: t('expedition.externalId'),
name: 'externalId', name: 'externalId',
field: 'externalId',
align: 'left', align: 'left',
columnFilter: null, cardVisible: true,
columnFilter: {
inWhere: true,
},
}, },
{ {
label: t('expedition.created'), label: t('expedition.created'),
name: 'created', name: 'created',
field: 'created',
align: 'left', align: 'left',
columnFilter: null, cardVisible: true,
format: (value) => toDateTimeFormat(value), format: (row) => toDateTimeFormat(row.created),
}, },
{ {
label: t('expedition.state'), label: t('expedition.state'),
name: 'state', name: 'state',
field: 'state',
align: 'left', align: 'left',
columnFilter: null, cardVisible: true,
columnFilter: { inWhere: true },
}, },
{ {
label: '', align: 'right',
name: 'history', name: 'tableActions',
align: 'left', actions: [
columnFilter: null, {
title: t('expedition.historyAction'),
icon: 'history',
isPrimary: true,
action: (row) => showLog(row),
},
],
}, },
]); ]);
@ -204,23 +156,29 @@ const logTableColumns = computed(() => [
label: t('expedition.state'), label: t('expedition.state'),
name: 'state', name: 'state',
field: 'state', field: 'state',
align: 'left', align: 'center',
sortable: true, sortable: true,
}, },
{ {
label: t('expedition.name'), label: t('expedition.name'),
name: 'name', name: 'name',
align: 'name', field: 'name',
align: 'center',
columnFilter: null, columnFilter: null,
}, },
{ {
label: t('expedition.created'), label: t('expedition.created'),
name: 'created', name: 'created',
field: 'created', field: 'created',
align: 'left', align: 'center',
columnFilter: null,
format: (value) => toDateTimeFormat(value), format: (value) => toDateTimeFormat(value),
}, },
{
label: t('expedition.isScanned'),
name: 'isScanned',
field: 'isScanned',
align: 'center',
},
]); ]);
const showNewTicketDialog = (withRoute = false) => { const showNewTicketDialog = (withRoute = false) => {
@ -255,10 +213,20 @@ const getExpeditionState = async (expedition) => {
order: ['created DESC'], order: ['created DESC'],
}; };
const { data } = await axios.get(`ExpeditionStates/filter`, { const { data: expeditionStates } = await axios.get(`ExpeditionStates/filter`, {
params: { filter: JSON.stringify(filter) }, params: { filter: JSON.stringify(filter) },
}); });
expeditionsLogsData.value = data; const { data: scannedStates } = await axios.get(`ExpeditionStates`, {
params: { filter: JSON.stringify(filter), fields: ['id', 'isScanned'] },
});
expeditionsLogsData.value = expeditionStates.map((state) => {
const scannedState = scannedStates.find((s) => s.id === state.id);
return {
...state,
isScanned: scannedState ? scannedState.isScanned : false,
};
});
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -275,21 +243,13 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<template> <template>
<VnSubToolbar> <VnSubToolbar>
<template #st-data>
<TableVisibleColumns
:all-columns="allColumnNames"
table-code="expeditionIndex"
labels-traductions-path="expedition"
@on-config-saved="visibleColumns = [...$event, 'history']"
/>
</template>
<template #st-actions> <template #st-actions>
<QBtnGroup push class="q-gutter-x-sm" flat> <QBtnGroup push class="q-gutter-x-sm" flat>
<QBtnDropdown <QBtnDropdown
ref="btnDropdownRef" ref="btnDropdownRef"
color="primary" color="primary"
:label="t('expedition.move')" :label="t('expedition.move')"
:disable="!selectedExpeditions.length" :disable="!hasSelectedRows"
> >
<template #label> <template #label>
<QTooltip>{{ t('Select lines to see the options') }}</QTooltip> <QTooltip>{{ t('Select lines to see the options') }}</QTooltip>
@ -322,7 +282,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</QList> </QList>
</QBtnDropdown> </QBtnDropdown>
<QBtn <QBtn
:disable="!selectedExpeditions.length" :disable="!hasSelectedRows"
icon="delete" icon="delete"
color="primary" color="primary"
@click=" @click="
@ -332,115 +292,34 @@ onUnmounted(() => (stateStore.rightDrawer = false));
deleteExpedition deleteExpedition
) )
" "
/> >
<QTooltip>{{ t('expedition.removeExpedition') }}</QTooltip>
</QBtn>
</QBtnGroup> </QBtnGroup>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QTable <VnTable
:rows="ticketExpeditions" ref="tableRef"
data-key="TicketExpedition"
url="Expeditions/filter"
:columns="columns" :columns="columns"
row-key="id" :filter="expeditionsFilter"
:pagination="{ rowsPerPage: 0 }" v-model:selected="selectedRows"
class="full-width q-mt-md" :table="{
selection="multiple" 'row-key': 'id',
v-model:selected="selectedExpeditions" selection: 'multiple',
:visible-columns="visibleColumns" }"
:no-data-label="t('globals.noResults')" auto-load
order="created DESC"
> >
<template #top-row="{ cols }"> <template #column-packagingItemFk="{ row }">
<QTr> <span class="link" @click.stop>
<QTd /> {{ row.packagingItemFk }}
<QTd v-for="(col, index) in cols" :key="index" style="max-width: 100px">
<component
:is="col.columnFilter.component"
v-if="col.columnFilter"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-item="{ row }">
<QTd auto-width @click.stop>
<QBtn flat color="primary">{{ row.packagingItemFk }}</QBtn>
<ItemDescriptorProxy :id="row.packagingItemFk" /> <ItemDescriptorProxy :id="row.packagingItemFk" />
</QTd> </span>
</template> </template>
<template #body-cell-available="{ row }"> </VnTable>
<QTd @click.stop>
<QBadge :color="row.available < 0 ? 'alert' : 'transparent'" dense>
{{ row.available }}
</QBadge>
</QTd>
</template>
<template #body-cell-price="{ row }">
<QTd>
<template v-if="isTicketEditable && row.id">
<QBtn flat color="primary" dense @click="onOpenEditPricePopover(row)">
{{ toCurrency(row.price) }}
</QBtn>
<TicketEditManaProxy
ref="editPriceProxyRef"
:mana="mana"
:new-price="getNewPrice"
@save="updatePrice(row)"
>
<VnInput
v-model.number="edit.price"
:label="t('ticketSale.price')"
type="number"
/>
</TicketEditManaProxy>
</template>
<span v-else>{{ toCurrency(row.price) }}</span>
</QTd>
</template>
<template #body-cell-discount="{ row }">
<QTd>
<template v-if="!isLocked && row.id">
<QBtn
flat
color="primary"
dense
@click="onOpenEditDiscountPopover(row)"
>
{{ toPercentage(row.discount / 100) }}
</QBtn>
<TicketEditManaProxy
:mana="mana"
:new-price="getNewPrice"
@save="changeDiscount(row)"
>
<VnInput
v-model.number="edit.discount"
:label="t('ticketSale.discount')"
type="number"
/>
</TicketEditManaProxy>
</template>
<span v-else>{{ toPercentage(row.discount / 100) }}</span>
</QTd>
</template>
<template #body-cell-history="{ row }">
<QTd>
<QBtn
@click.stop="showLog(row)"
color="primary"
icon="history"
size="md"
flat
>
<QTooltip class="text-no-wrap">
{{ t('expedition.historyAction') }}
</QTooltip>
</QBtn>
</QTd>
</template>
</QTable>
<QDialog ref="newTicketDialogRef" transition-show="scale" transition-hide="scale"> <QDialog ref="newTicketDialogRef" transition-show="scale" transition-hide="scale">
<ExpeditionNewTicket <ExpeditionNewTicket
:ticket="ticketData" :ticket="ticketData"
@ -454,12 +333,23 @@ onUnmounted(() => (stateStore.rightDrawer = false));
data-key="TicketExpeditionLog" data-key="TicketExpeditionLog"
:rows="expeditionsLogsData" :rows="expeditionsLogsData"
:columns="logTableColumns" :columns="logTableColumns"
class="q-pa-sm" class="q-pa-md full-width"
> >
<template #body-cell-name="{ row }"> <template #body-cell-name="{ row }">
<QTd auto-width> <QTd style="text-align: center">
<QBtn flat dense color="primary">{{ row.name }}</QBtn> <span class="link" @click.stop>
<WorkerDescriptorProxy :id="row.workerFk" /> <QBtn flat dense>{{ row.name }}</QBtn>
<WorkerDescriptorProxy :id="row.workerFk" />
</span>
</QTd>
</template>
<template #body-cell-isScanned="{ row }">
<QTd style="text-align: center">
<QCheckbox disable v-model="row.isScanned">
{{
row.isScanned === 1 ? t('expedition.yes') : t('expedition.no')
}}
</QCheckbox>
</QTd> </QTd>
</template> </template>
</QTable> </QTable>

Some files were not shown because too many files have changed in this diff Show More