feat: refs #8001 ticketExpeditionGrafana #865
84
CHANGELOG.md
84
CHANGELOG.md
|
@ -1,3 +1,87 @@
|
|||
# Version 24.50 - 2024-12-10
|
||||
|
||||
### Added 🆕
|
||||
|
||||
- feat: add reportFileName option by:Javier Segarra
|
||||
- feat: all clients just with global series by:jgallego
|
||||
- feat: improve Merge branch 'test' into dev by:Javier Segarra
|
||||
- feat: manual invoice in two lines by:jgallego
|
||||
- feat: manualInvoice with address by:jgallego
|
||||
- feat: randomize functions and example by:Javier Segarra
|
||||
- feat: refs #6999 added search when user tabs on a filter with value by:Jon
|
||||
- feat: refs #6999 added tab to search in VnTable filter by:Jon
|
||||
- feat: refs #7346 #7346 improve form by:Javier Segarra
|
||||
- feat: refs #7346 address ordered by:jgallego
|
||||
- feat: refs #7346 radioButton by:jgallego
|
||||
- feat: refs #7346 style radioButton by:jgallego
|
||||
- feat: refs #7346 traducciones en cammelCase (7346-manualInvoice) by:jgallego
|
||||
- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon
|
||||
- feat: refs #8061 #8061 updates by:Javier Segarra
|
||||
- feat: refs #8087 reactive data by:jorgep
|
||||
- feat: refs #8087 refs#8087 Redadas en travel by:Carlos Andrés
|
||||
- feat: refs #8138 add component ticket problems by:pablone
|
||||
- feat: refs #8163 add max length and more tests by:wbuezas
|
||||
- feat: refs #8163 add prop by:wbuezas
|
||||
- feat: refs #8163 add VnInput insert functionality and e2e test by:wbuezas
|
||||
- feat: refs #8163 limit with maxLength by:Javier Segarra
|
||||
- feat: refs #8163 maxLength SupplierFD account by:Javier Segarra
|
||||
- feat: refs #8163 maxLengthVnInput by:Javier Segarra
|
||||
- feat: refs #8163 use VnAccountNumber in VnAccountNumber by:Javier Segarra
|
||||
- feat: refs #8166 show notification by:jorgep
|
||||
|
||||
### Changed 📦
|
||||
|
||||
- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon
|
||||
- perf: add dataCy by:Javier Segarra
|
||||
- perf: refs #7346 #7346 Imrpove interface dialog by:Javier Segarra
|
||||
- perf: refs #7346 #7346 use v-show instead v-if by:Javier Segarra
|
||||
- perf: refs #8036 currentFilter by:alexm
|
||||
- perf: refs #8061 filter autonomy by:Javier Segarra
|
||||
- perf: refs #8061 solve conflicts and random posCode it by:Javier Segarra
|
||||
- perf: refs #8061 use opts from VnSelect by:Javier Segarra
|
||||
- perf: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra
|
||||
- perf: remove console by:Javier Segarra
|
||||
- perf: remove timeout by:Javier Segarra
|
||||
- perf: test command fillInForm by:Javier Segarra
|
||||
- refactor: refs #8162 remove comment by:wbuezas
|
||||
- refactor: remove unnecesary things by:wbuezas
|
||||
|
||||
### Fixed 🛠️
|
||||
|
||||
- fix: #8016 fetching data by:Javier Segarra
|
||||
- fix: icons by:jgallego
|
||||
- fix: refs #7229 download file by:jorgep
|
||||
- fix: refs #7229 remove catch by:jorgep
|
||||
- fix: refs #7229 set url by:jorgep
|
||||
- fix: refs #7229 test by:jorgep
|
||||
- fix: refs #7229 url by:jorgep
|
||||
- fix: refs #7229 url + test by:jorgep
|
||||
- fix: refs #7304 7304 clean warning by:carlossa
|
||||
- fix: refs #7304 fix list by:carlossa
|
||||
- fix: refs #7304 fix warning by:carlossa
|
||||
- fix: refs #7346 traslations by:jgallego
|
||||
- fix: refs #7529 add save by:carlossa
|
||||
- fix: refs #7529 fix e2e by:carlossa
|
||||
- fix: refs #7529 fix front by:carlossa
|
||||
- fix: refs #7529 fix scss by:carlossa
|
||||
- fix: refs #7529 fix te2e by:carlossa
|
||||
- fix: refs #7529 fix workerPit e2e by:carlossa
|
||||
- fix: refs #7529 front by:carlossa
|
||||
- fix: refs #8036 apply exprBuilder after save filters by:alexm
|
||||
- fix: refs #8036 only add where when required by:alexm
|
||||
- fix: refs #8038 solve conflicts by:Jon
|
||||
- fix: refs #8061 improve code dependencies (origin/8061_improve_newCP) by:Javier Segarra
|
||||
- fix: refs #8138 move component from ui folder by:pablone
|
||||
- fix: refs #8138 sme minor issues by:pablone
|
||||
- fix: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra
|
||||
- fix: refs #8163 minor problem when keypress by:Javier Segarra
|
||||
- fix: refs #8166 show zone error by:jorgep
|
||||
- fix: removed selectedClient by:jgallego
|
||||
- refs #7529 fix workerPit by:carlossa
|
||||
- revert: refs #8061 test #8061 updates by:Javier Segarra
|
||||
- test: fix own test by:Javier Segarra
|
||||
- test: refs #8162 #8162 fix TicketList spec by:Javier Segarra
|
||||
|
||||
# Version 24.48 - 2024-11-25
|
||||
|
||||
### Added 🆕
|
||||
|
|
|
@ -4,7 +4,8 @@ def PROTECTED_BRANCH
|
|||
|
||||
def BRANCH_ENV = [
|
||||
test: 'test',
|
||||
master: 'production'
|
||||
master: 'production',
|
||||
beta: 'production'
|
||||
]
|
||||
|
||||
node {
|
||||
|
@ -15,7 +16,8 @@ node {
|
|||
PROTECTED_BRANCH = [
|
||||
'dev',
|
||||
'test',
|
||||
'master'
|
||||
'master',
|
||||
'beta'
|
||||
].contains(env.BRANCH_NAME)
|
||||
|
||||
// https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.50.0",
|
||||
"version": "24.52.0",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
@ -64,4 +64,4 @@
|
|||
"vite": "^5.1.4",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import routes from 'src/router/modules';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
let isNotified = false;
|
||||
|
||||
export default {
|
||||
created: function () {
|
||||
const router = useRouter();
|
||||
const keyBindingMap = routes
|
||||
.filter((route) => route.meta.keyBinding)
|
||||
.reduce((map, route) => {
|
||||
map['Key' + route.meta.keyBinding.toUpperCase()] = route.path;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
const { ctrlKey, altKey, code } = event;
|
||||
|
||||
if (ctrlKey && altKey && keyBindingMap[code] && !isNotified) {
|
||||
event.preventDefault();
|
||||
router.push(keyBindingMap[code]);
|
||||
isNotified = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (event) => {
|
||||
const { ctrlKey, altKey } = event;
|
||||
if (!ctrlKey || !altKey) {
|
||||
isNotified = false;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
},
|
||||
};
|
|
@ -1,30 +1,52 @@
|
|||
import { getCurrentInstance } from 'vue';
|
||||
|
||||
function focusFirstInput(input) {
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
export default {
|
||||
mounted: function () {
|
||||
const vm = getCurrentInstance();
|
||||
if (vm.type.name === 'QForm') {
|
||||
if (!['searchbarForm', 'filterPanelForm'].includes(this.$el?.id)) {
|
||||
// TODO: AUTOFOCUS IS NOT FOCUSING
|
||||
const that = this;
|
||||
this.$el.addEventListener('keyup', function (evt) {
|
||||
if (evt.key === 'Enter') {
|
||||
const input = evt.target;
|
||||
if (input.type == 'textarea' && evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
let { selectionStart, selectionEnd } = input;
|
||||
input.value =
|
||||
input.value.substring(0, selectionStart) +
|
||||
'\n' +
|
||||
input.value.substring(selectionEnd);
|
||||
selectionStart = selectionEnd = selectionStart + 1;
|
||||
return;
|
||||
}
|
||||
evt.preventDefault();
|
||||
that.onSubmit();
|
||||
}
|
||||
});
|
||||
const that = this;
|
||||
|
||||
const form = document.querySelector('.q-form#formModel');
|
||||
if (!form) return;
|
||||
try {
|
||||
const inputsFormCard = form.querySelectorAll(
|
||||
`input:not([disabled]):not([type="checkbox"])`
|
||||
);
|
||||
if (inputsFormCard.length) {
|
||||
focusFirstInput(inputsFormCard[0]);
|
||||
}
|
||||
const textareas = document.querySelectorAll(
|
||||
'textarea:not([disabled]), [contenteditable]:not([disabled])'
|
||||
);
|
||||
if (textareas.length) {
|
||||
focusFirstInput(textareas[textareas.length - 1]);
|
||||
}
|
||||
const inputs = document.querySelectorAll(
|
||||
'form#formModel input:not([disabled]):not([type="checkbox"])'
|
||||
);
|
||||
const input = inputs[0];
|
||||
if (!input) return;
|
||||
|
||||
focusFirstInput(input);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
form.addEventListener('keyup', function (evt) {
|
||||
if (evt.key === 'Enter') {
|
||||
const input = evt.target;
|
||||
if (input.type == 'textarea' && evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
let { selectionStart, selectionEnd } = input;
|
||||
input.value =
|
||||
input.value.substring(0, selectionStart) +
|
||||
'\n' +
|
||||
input.value.substring(selectionEnd);
|
||||
selectionStart = selectionEnd = selectionStart + 1;
|
||||
return;
|
||||
}
|
||||
evt.preventDefault();
|
||||
that.onSubmit();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import axios from 'axios';
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import qFormMixin from './qformMixin';
|
||||
import keyShortcut from './keyShortcut';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { CanceledError } from 'axios';
|
||||
|
||||
const { notify } = useNotify();
|
||||
import { QForm } from 'quasar';
|
||||
import { QLayout } from 'quasar';
|
||||
import mainShortcutMixin from './mainShortcutMixin';
|
||||
import { useCau } from 'src/composables/useCau';
|
||||
|
||||
export default boot(({ app }) => {
|
||||
app.mixin(qFormMixin);
|
||||
QForm.mixins = [qFormMixin];
|
||||
QLayout.mixins = [mainShortcutMixin];
|
||||
|
||||
app.directive('shortcut', keyShortcut);
|
||||
app.config.errorHandler = (error) => {
|
||||
app.config.errorHandler = async (error) => {
|
||||
let message;
|
||||
const response = error.response;
|
||||
const responseData = response?.data;
|
||||
|
@ -40,12 +43,12 @@ export default boot(({ app }) => {
|
|||
}
|
||||
|
||||
console.error(error);
|
||||
if (error instanceof CanceledError) {
|
||||
if (error instanceof axios.CanceledError) {
|
||||
const env = process.env.NODE_ENV;
|
||||
if (env && env !== 'development') return;
|
||||
message = 'Duplicate request';
|
||||
}
|
||||
|
||||
notify(message ?? 'globals.error', 'negative', 'error');
|
||||
await useCau(response, message);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -40,6 +40,7 @@ const onDataSaved = (...args) => {
|
|||
url-create="towns"
|
||||
model="city"
|
||||
@on-data-saved="onDataSaved"
|
||||
data-cy="newCityForm"
|
||||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow>
|
||||
|
@ -48,12 +49,14 @@ const onDataSaved = (...args) => {
|
|||
v-model="data.name"
|
||||
:rules="validate('city.name')"
|
||||
required
|
||||
data-cy="cityName"
|
||||
/>
|
||||
<VnSelectProvince
|
||||
:province-selected="$props.provinceSelected"
|
||||
:country-fk="$props.countryFk"
|
||||
v-model="data.provinceFk"
|
||||
required
|
||||
data-cy="provinceCity"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
@ -21,14 +21,13 @@ const postcodeFormData = reactive({
|
|||
provinceFk: null,
|
||||
townFk: null,
|
||||
});
|
||||
|
||||
const townsFetchDataRef = ref(false);
|
||||
const countriesRef = ref(false);
|
||||
const townsRef = ref(false);
|
||||
const provincesFetchDataRef = ref(false);
|
||||
const provincesOptions = ref([]);
|
||||
const town = ref({});
|
||||
const townFilter = ref({});
|
||||
|
||||
const countriesRef = ref(false);
|
||||
const provincesOptions = ref([]);
|
||||
const townsOptions = ref([]);
|
||||
const town = ref({});
|
||||
const countryFilter = ref({});
|
||||
|
||||
function onDataSaved(formData) {
|
||||
|
@ -48,62 +47,87 @@ function onDataSaved(formData) {
|
|||
emit('onDataSaved', newPostcode);
|
||||
}
|
||||
|
||||
async function setCountry(countryFk, data) {
|
||||
data.townFk = null;
|
||||
data.provinceFk = null;
|
||||
data.countryFk = countryFk;
|
||||
await fetchTowns();
|
||||
}
|
||||
|
||||
// Province
|
||||
|
||||
async function handleProvinces(data) {
|
||||
provincesOptions.value = data;
|
||||
if (postcodeFormData.countryFk) {
|
||||
await fetchTowns();
|
||||
}
|
||||
}
|
||||
async function setProvince(id, data) {
|
||||
if (data.provinceFk === id) return;
|
||||
const newProvince = provincesOptions.value.find((province) => province.id == id);
|
||||
if (newProvince) data.countryFk = newProvince.countryFk;
|
||||
postcodeFormData.provinceFk = id;
|
||||
await fetchTowns();
|
||||
}
|
||||
async function onProvinceCreated(data) {
|
||||
postcodeFormData.provinceFk = data.id;
|
||||
}
|
||||
function provinceByCountry(countryFk = postcodeFormData.countryFk) {
|
||||
return provincesOptions.value
|
||||
.filter((province) => province.countryFk === countryFk)
|
||||
.map(({ id }) => id);
|
||||
}
|
||||
|
||||
// Town
|
||||
async function handleTowns(data) {
|
||||
townsOptions.value = data;
|
||||
}
|
||||
function setTown(newTown, data) {
|
||||
town.value = newTown;
|
||||
data.provinceFk = newTown?.provinceFk ?? newTown;
|
||||
data.countryFk = newTown?.province?.countryFk ?? newTown;
|
||||
}
|
||||
async function onCityCreated(newTown, formData) {
|
||||
await provincesFetchDataRef.value.fetch();
|
||||
newTown.province = provincesOptions.value.find(
|
||||
(province) => province.id === newTown.provinceFk
|
||||
);
|
||||
formData.townFk = newTown;
|
||||
setTown(newTown, formData);
|
||||
}
|
||||
|
||||
function setTown(newTown, data) {
|
||||
town.value = newTown;
|
||||
data.provinceFk = newTown?.provinceFk ?? newTown;
|
||||
data.countryFk = newTown?.province?.countryFk ?? newTown;
|
||||
}
|
||||
|
||||
async function setCountry(countryFk, data) {
|
||||
data.townFk = null;
|
||||
data.provinceFk = null;
|
||||
data.countryFk = countryFk;
|
||||
}
|
||||
|
||||
async function handleProvinces(data) {
|
||||
provincesOptions.value = data;
|
||||
}
|
||||
|
||||
async function setProvince(id, data) {
|
||||
const newProvince = provincesOptions.value.find((province) => province.id == id);
|
||||
if (!newProvince) return;
|
||||
|
||||
data.countryFk = newProvince.countryFk;
|
||||
}
|
||||
|
||||
async function onProvinceCreated(data) {
|
||||
await provincesFetchDataRef.value.fetch({
|
||||
where: { countryFk: postcodeFormData.countryFk },
|
||||
});
|
||||
postcodeFormData.provinceFk = data.id;
|
||||
}
|
||||
|
||||
const whereTowns = computed(() => {
|
||||
return {
|
||||
async function fetchTowns(countryFk = postcodeFormData.countryFk) {
|
||||
if (!countryFk) return;
|
||||
const provinces = postcodeFormData.provinceFk
|
||||
? [postcodeFormData.provinceFk]
|
||||
: provinceByCountry();
|
||||
townFilter.value.where = {
|
||||
provinceFk: {
|
||||
inq: provincesOptions.value.map(({ id }) => id),
|
||||
inq: provinces,
|
||||
},
|
||||
};
|
||||
});
|
||||
await townsFetchDataRef.value?.fetch();
|
||||
}
|
||||
|
||||
async function filterTowns(name) {
|
||||
if (name !== '') {
|
||||
townFilter.value.where = {
|
||||
name: {
|
||||
like: `%${name}%`,
|
||||
},
|
||||
};
|
||||
await townsFetchDataRef.value?.fetch();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="provincesFetchDataRef"
|
||||
@on-fetch="handleProvinces"
|
||||
ref="townsFetchDataRef"
|
||||
:sort-by="['name ASC']"
|
||||
:limit="30"
|
||||
:filter="townFilter"
|
||||
@on-fetch="handleTowns"
|
||||
auto-load
|
||||
url="Provinces/location"
|
||||
url="Towns/location"
|
||||
/>
|
||||
|
||||
<FormModelPopup
|
||||
|
@ -123,25 +147,22 @@ const whereTowns = computed(() => {
|
|||
:rules="validate('postcode.code')"
|
||||
clearable
|
||||
required
|
||||
data-cy="locationPostcode"
|
||||
/>
|
||||
<VnSelectDialog
|
||||
ref="townsRef"
|
||||
:sort-by="['name ASC']"
|
||||
:limit="30"
|
||||
auto-load
|
||||
url="Towns/location"
|
||||
:where="whereTowns"
|
||||
:label="t('City')"
|
||||
@update:model-value="(value) => setTown(value, data)"
|
||||
@filter="filterTowns"
|
||||
:tooltip="t('Create city')"
|
||||
v-model="data.townFk"
|
||||
:options="townsOptions"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
:rules="validate('postcode.city')"
|
||||
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
|
||||
:emit-value="false"
|
||||
:clearable="true"
|
||||
required
|
||||
data-cy="locationTown"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
|
@ -171,8 +192,12 @@ const whereTowns = computed(() => {
|
|||
:country-fk="data.countryFk"
|
||||
:province-selected="data.provinceFk"
|
||||
@update:model-value="(value) => setProvince(value, data)"
|
||||
@update:options="
|
||||
(data) => {
|
||||
provincesOptions = data;
|
||||
}
|
||||
"
|
||||
v-model="data.provinceFk"
|
||||
@on-province-fetched="handleProvinces"
|
||||
@on-province-created="onProvinceCreated"
|
||||
required
|
||||
/>
|
||||
|
@ -191,6 +216,7 @@ const whereTowns = computed(() => {
|
|||
v-model="data.countryFk"
|
||||
:rules="validate('postcode.countryFk')"
|
||||
@update:model-value="(value) => setCountry(value, data)"
|
||||
data-cy="locationCountry"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
|
|
|
@ -53,8 +53,10 @@ const where = computed(() => {
|
|||
v-model="data.name"
|
||||
:rules="validate('province.name')"
|
||||
required
|
||||
data-cy="provinceName"
|
||||
/>
|
||||
<VnSelect
|
||||
data-cy="autonomyProvince"
|
||||
required
|
||||
ref="autonomiesRef"
|
||||
auto-load
|
||||
|
|
|
@ -176,14 +176,13 @@ async function saveChanges(data) {
|
|||
const changes = data || getChanges();
|
||||
try {
|
||||
await axios.post($props.saveUrl || $props.url + '/crud', changes);
|
||||
} catch (e) {
|
||||
return (isLoading.value = false);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
originalData.value = JSON.parse(JSON.stringify(formData.value));
|
||||
if (changes.creates?.length) await vnPaginateRef.value.fetch();
|
||||
|
||||
hasChanges.value = false;
|
||||
isLoading.value = false;
|
||||
emit('saveChanges', data);
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
|
@ -250,7 +249,7 @@ function getChanges() {
|
|||
for (const [i, row] of formData.value.entries()) {
|
||||
if (!row[pk]) {
|
||||
creates.push(row);
|
||||
} else if (originalData.value) {
|
||||
} else if (originalData.value[i]) {
|
||||
const data = getDifferences(originalData.value[i], row);
|
||||
if (!isEmpty(data)) {
|
||||
updates.push({
|
||||
|
|
|
@ -85,12 +85,14 @@ const closeForm = () => {
|
|||
hide-selected
|
||||
option-label="label"
|
||||
v-model="selectedField"
|
||||
data-cy="field-to-edit"
|
||||
/>
|
||||
<component
|
||||
:is="inputs[selectedField?.component || 'input']"
|
||||
v-bind="selectedField?.attrs || {}"
|
||||
v-model="newValue"
|
||||
:label="t('Value')"
|
||||
data-cy="value-to-edit"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</VnRow>
|
||||
|
|
|
@ -62,6 +62,7 @@ defineExpose({
|
|||
@click="emit('onDataCanceled')"
|
||||
v-close-popup
|
||||
data-cy="FormModelPopup_cancel"
|
||||
z-max
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
|
@ -72,6 +73,7 @@ defineExpose({
|
|||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
data-cy="FormModelPopup_save"
|
||||
z-max
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,6 +9,8 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
import { getParamWhere } from 'src/filters';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -26,28 +28,21 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const itemCategories = ref([]);
|
||||
const selectedCategoryFk = ref(null);
|
||||
const selectedTypeFk = ref(null);
|
||||
const route = useRoute();
|
||||
|
||||
const itemTypesOptions = ref([]);
|
||||
const suppliersOptions = ref([]);
|
||||
const tagOptions = ref([]);
|
||||
const tagValues = ref([]);
|
||||
const categoryList = ref(null);
|
||||
const selectedCategoryFk = ref(getParamWhere(route.query.table, 'categoryFk', false));
|
||||
const selectedTypeFk = ref(getParamWhere(route.query.table, 'typeFk', false));
|
||||
|
||||
const categoryList = computed(() => {
|
||||
return (itemCategories.value || [])
|
||||
.filter((category) => category.display)
|
||||
.map((category) => ({
|
||||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
});
|
||||
|
||||
const selectedCategory = computed(() =>
|
||||
(itemCategories.value || []).find(
|
||||
const selectedCategory = computed(() => {
|
||||
return (categoryList.value || []).find(
|
||||
(category) => category?.id === selectedCategoryFk.value
|
||||
)
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
const selectedType = computed(() => {
|
||||
return (itemTypesOptions.value || []).find(
|
||||
|
@ -87,7 +82,7 @@ const applyTags = (params, search) => {
|
|||
search();
|
||||
};
|
||||
|
||||
const fetchItemTypes = async (id) => {
|
||||
const fetchItemTypes = async (id = selectedCategoryFk.value) => {
|
||||
const filter = {
|
||||
fields: ['id', 'name', 'categoryFk'],
|
||||
where: { categoryFk: id },
|
||||
|
@ -126,15 +121,19 @@ const removeTag = (index, params, search) => {
|
|||
(tagValues.value || []).splice(index, 1);
|
||||
applyTags(params, search);
|
||||
};
|
||||
const setCategoryList = (data) => {
|
||||
categoryList.value = (data || [])
|
||||
.filter((category) => category.display)
|
||||
.map((category) => ({
|
||||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
fetchItemTypes();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="ItemCategories"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemCategories = data)"
|
||||
/>
|
||||
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
limit="30"
|
||||
|
|
|
@ -177,6 +177,7 @@ function normalize(text) {
|
|||
class="full-width"
|
||||
filled
|
||||
dense
|
||||
autofocus
|
||||
/>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
|
|
|
@ -7,7 +7,7 @@ import VnSelectDialog from 'components/common/VnSelectDialog.vue';
|
|||
import FetchData from 'components/FetchData.vue';
|
||||
import CreateNewProvinceForm from './CreateNewProvinceForm.vue';
|
||||
|
||||
const emit = defineEmits(['onProvinceCreated', 'onProvinceFetched']);
|
||||
const emit = defineEmits(['onProvinceCreated', 'onProvinceFetched', 'update:options']);
|
||||
const $props = defineProps({
|
||||
countryFk: {
|
||||
type: Number,
|
||||
|
@ -41,6 +41,7 @@ async function onProvinceCreated(_, data) {
|
|||
}
|
||||
async function handleProvinces(data) {
|
||||
provincesOptions.value = data;
|
||||
emit('update:options', data);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@ -64,11 +65,11 @@ watch(
|
|||
auto-load
|
||||
/>
|
||||
<VnSelectDialog
|
||||
data-cy="locationProvince"
|
||||
:label="t('Province')"
|
||||
:options="provincesOptions"
|
||||
:tooltip="t('Create province')"
|
||||
hide-selected
|
||||
:clearable="true"
|
||||
v-model="provinceFk"
|
||||
:rules="validate && validate('postcode.provinceFk')"
|
||||
:acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"
|
||||
|
|
|
@ -610,6 +610,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
|
|||
$props.rowClick && $props.rowClick(row);
|
||||
}
|
||||
"
|
||||
style="height: 100%"
|
||||
>
|
||||
<QCardSection
|
||||
vertical
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<script setup>
|
||||
import { toDateFormat } from 'src/filters/date.js';
|
||||
|
||||
defineProps({ date: { type: [Date, String], required: true } });
|
||||
|
||||
function getBadgeAttrs(date) {
|
||||
let today = Date.vnNew();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
let timeTicket = new Date(date);
|
||||
timeTicket.setHours(0, 0, 0, 0);
|
||||
|
||||
let timeDiff = today - timeTicket;
|
||||
|
||||
if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' };
|
||||
if (timeDiff < 0) return { color: 'success', 'text-color': 'black' };
|
||||
return { color: 'transparent', 'text-color': 'white' };
|
||||
}
|
||||
|
||||
function formatShippedDate(date) {
|
||||
if (!date) return '-';
|
||||
const dateSplit = date.split('T');
|
||||
const [year, month, day] = dateSplit[0].split('-');
|
||||
const newDate = new Date(year, month - 1, day);
|
||||
return toDateFormat(newDate);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<QBadge v-bind="getBadgeAttrs(date)" class="q-pa-sm" style="font-size: 14px">
|
||||
{{ formatShippedDate(date) }}
|
||||
</QBadge>
|
||||
</template>
|
|
@ -2,7 +2,7 @@
|
|||
import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
|
||||
import VnSelectDialog from 'components/common/VnSelectDialog.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useAttrs } from 'vue';
|
||||
import { useRequired } from 'src/composables/useRequired';
|
||||
const { t } = useI18n();
|
||||
|
@ -43,7 +43,7 @@ const formatLocation = (obj, properties) => {
|
|||
return filteredParts.join(', ');
|
||||
};
|
||||
|
||||
const modelValue = ref(
|
||||
const modelValue = computed(() =>
|
||||
props.location ? formatLocation(props.location, locationProperties) : null
|
||||
);
|
||||
|
||||
|
@ -75,7 +75,6 @@ const handleModelValue = (data) => {
|
|||
:input-debounce="300"
|
||||
:class="{ required: isRequired }"
|
||||
v-bind="$attrs"
|
||||
clearable
|
||||
:emit-value="false"
|
||||
:tooltip="t('Create new location')"
|
||||
:rules="mixinRules"
|
||||
|
|
|
@ -238,6 +238,7 @@ async function openPointRecord(id, modelLog) {
|
|||
pointRecord.value = parseProps(propNames, locale, data);
|
||||
}
|
||||
async function setLogTree(data) {
|
||||
if (!data) return;
|
||||
logTree.value = getLogTree(data);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,14 @@ import dataByOrder from 'src/utils/dataByOrder';
|
|||
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
|
||||
const $attrs = useAttrs();
|
||||
const { t } = useI18n();
|
||||
const { isRequired, requiredFieldRule } = useRequired($attrs);
|
||||
|
||||
const isRequired = computed(() => {
|
||||
return useRequired($attrs).isRequired;
|
||||
});
|
||||
const requiredFieldRule = computed(() => {
|
||||
return useRequired($attrs).requiredFieldRule;
|
||||
});
|
||||
|
||||
const $props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Object],
|
||||
|
@ -261,7 +268,7 @@ async function onScroll({ to, direction, from, index }) {
|
|||
defineExpose({ opts: myOptions });
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === 'Tab') {
|
||||
if (event.key === 'Tab' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
|
||||
const inputValue = vnSelectRef.value?.inputValue;
|
||||
|
@ -279,6 +286,17 @@ function handleKeyDown(event) {
|
|||
}
|
||||
vnSelectRef.value?.hidePopup();
|
||||
}
|
||||
|
||||
const focusableElements = document.querySelectorAll(
|
||||
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
const currentIndex = Array.prototype.indexOf.call(
|
||||
focusableElements,
|
||||
event.target
|
||||
);
|
||||
if (currentIndex >= 0 && currentIndex < focusableElements.length - 1) {
|
||||
focusableElements[currentIndex + 1].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -308,9 +326,9 @@ function handleKeyDown(event) {
|
|||
@virtual-scroll="onScroll"
|
||||
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
|
||||
>
|
||||
<template v-if="isClearable" #append>
|
||||
<template #append>
|
||||
<QIcon
|
||||
v-show="value"
|
||||
v-show="isClearable && value"
|
||||
name="close"
|
||||
@click.stop="
|
||||
() => {
|
||||
|
@ -323,7 +341,22 @@ function handleKeyDown(event) {
|
|||
/>
|
||||
</template>
|
||||
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
|
||||
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
|
||||
<div v-if="slotName == 'append'">
|
||||
<QIcon
|
||||
v-show="isClearable && value"
|
||||
name="close"
|
||||
@click.stop="
|
||||
() => {
|
||||
value = null;
|
||||
emit('remove');
|
||||
}
|
||||
"
|
||||
class="cursor-pointer"
|
||||
size="xs"
|
||||
/>
|
||||
<slot name="append" v-if="$slots.append" v-bind="slotData ?? {}" />
|
||||
</div>
|
||||
<slot v-else :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
|
||||
</template>
|
||||
</QSelect>
|
||||
</template>
|
||||
|
|
|
@ -43,6 +43,7 @@ const isAllowedToCreate = computed(() => {
|
|||
>
|
||||
<template v-if="isAllowedToCreate" #append>
|
||||
<QIcon
|
||||
:data-cy="$attrs.dataCy ?? $attrs.label + '_icon'"
|
||||
@click.stop.prevent="$refs.dialog.show()"
|
||||
:name="actionIcon"
|
||||
:size="actionIcon === 'add' ? 'xs' : 'sm'"
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<script setup>
|
||||
import { computed, useAttrs } from 'vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnAvatar from 'src/components/ui/VnAvatar.vue';
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const $props = defineProps({
|
||||
hasAvatar: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hasInfo: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number, Object],
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const $attrs = useAttrs();
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
return $props.modelValue;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:modelValue', val);
|
||||
},
|
||||
});
|
||||
|
||||
const url = computed(() => {
|
||||
let url = 'Workers/search';
|
||||
const { departmentCodes } = $attrs.params ?? {};
|
||||
if (!departmentCodes) return url;
|
||||
const params = new URLSearchParams({
|
||||
departmentCodes: JSON.stringify(departmentCodes),
|
||||
});
|
||||
|
||||
return url.concat(`?${params.toString()}`);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSelect
|
||||
:label="$t('globals.worker')"
|
||||
v-bind="$attrs"
|
||||
v-model="value"
|
||||
:url="url"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
:fields="['id', 'name', 'nickname', 'code']"
|
||||
sort-by="nickname ASC"
|
||||
>
|
||||
<template #prepend v-if="$props.hasAvatar">
|
||||
<VnAvatar :worker-id="value" color="primary" :title="title" />
|
||||
</template>
|
||||
<template #append v-if="$props.hasInfo">
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>{{ $t($props.hasInfo) }}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt.name }}
|
||||
</QItemLabel>
|
||||
<QItemLabel v-if="!scope.opt.id">
|
||||
{{ scope.opt.nickname }}
|
||||
</QItemLabel>
|
||||
<QItemLabel caption v-else>
|
||||
{{ scope.opt.nickname }}, {{ scope.opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Responsible for approving invoices: Responsable de aprobar las facturas
|
||||
</i18n>
|
|
@ -222,8 +222,8 @@ const toModule = computed(() =>
|
|||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.body {
|
||||
<style lang="scss" scoped>
|
||||
:deep(.body) {
|
||||
background-color: var(--vn-section-color);
|
||||
.text-h5 {
|
||||
font-size: 20px;
|
||||
|
@ -262,9 +262,7 @@ const toModule = computed(() =>
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, toRef } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
|
@ -13,7 +13,7 @@ const DEFAULT_PRICE_KG = 0;
|
|||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -25,57 +25,63 @@ defineProps({
|
|||
});
|
||||
|
||||
const dialog = ref(null);
|
||||
const card = toRef(props, 'item');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container order-catalog-item overflow-hidden">
|
||||
<QCard class="card shadow-6">
|
||||
<div class="img-wrapper">
|
||||
<VnImg :id="item.id" class="image" zoom-resolution="1600x900" />
|
||||
<div v-if="item.hex && isCatalog" class="item-color-container">
|
||||
<VnImg :id="card.id" class="image" zoom-resolution="1600x900" />
|
||||
<div v-if="card.hex && isCatalog" class="item-color-container">
|
||||
<div
|
||||
class="item-color"
|
||||
:style="{ backgroundColor: `#${item.hex}` }"
|
||||
:style="{ backgroundColor: `#${card.hex}` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<span class="link">
|
||||
{{ item.name }}
|
||||
<ItemDescriptorProxy :id="item.id" />
|
||||
{{ card.name }}
|
||||
<ItemDescriptorProxy :id="card.id" />
|
||||
</span>
|
||||
<p class="subName">{{ item.subName }}</p>
|
||||
<p class="subName">{{ card.subName }}</p>
|
||||
<template v-for="index in 4" :key="`tag-${index}`">
|
||||
<VnLv
|
||||
v-if="item?.[`tag${index + 4}`]"
|
||||
:label="item?.[`tag${index + 4}`] + ':'"
|
||||
:value="item?.[`value${index + 4}`]"
|
||||
v-if="card?.[`tag${index + 4}`]"
|
||||
:label="card?.[`tag${index + 4}`] + ':'"
|
||||
:value="card?.[`value${index + 4}`]"
|
||||
/>
|
||||
</template>
|
||||
<div v-if="item.minQuantity" class="min-quantity">
|
||||
<div v-if="card.minQuantity" class="min-quantity">
|
||||
<QIcon name="production_quantity_limits" size="xs" />
|
||||
{{ item.minQuantity }}
|
||||
{{ card.minQuantity }}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="price">
|
||||
<p v-if="isCatalog">
|
||||
{{ item.available }} {{ t('to') }}
|
||||
{{ toCurrency(item.price) }}
|
||||
{{ card.available }} {{ t('to') }}
|
||||
{{ toCurrency(card.price) }}
|
||||
</p>
|
||||
<slot name="price" />
|
||||
<QIcon v-if="isCatalog" name="add_circle" class="icon">
|
||||
<QTooltip>{{ t('globals.add') }}</QTooltip>
|
||||
<QPopupProxy ref="dialog">
|
||||
<OrderCatalogItemDialog
|
||||
:prices="item.prices"
|
||||
@added="() => dialog.hide()"
|
||||
:item="card"
|
||||
@added="
|
||||
(quantityAdded) => {
|
||||
card.available += quantityAdded;
|
||||
dialog.hide();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</div>
|
||||
<p v-if="item.priceKg" class="price-kg">
|
||||
<p v-if="card.priceKg" class="price-kg">
|
||||
{{ t('price-kg') }}
|
||||
{{ toCurrency(item.priceKg) || DEFAULT_PRICE_KG }}
|
||||
{{ toCurrency(card.priceKg) || DEFAULT_PRICE_KG }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useColor } from 'src/composables/useColor';
|
|||
import { getCssVar } from 'quasar';
|
||||
|
||||
const $props = defineProps({
|
||||
workerId: { type: Number, required: true },
|
||||
workerId: { type: [Number, undefined], default: null },
|
||||
description: { type: String, default: null },
|
||||
title: { type: String, default: null },
|
||||
color: { type: String, default: null },
|
||||
|
@ -38,7 +38,13 @@ watch(src, () => (showLetter.value = false));
|
|||
<template v-if="showLetter">
|
||||
{{ title.charAt(0) }}
|
||||
</template>
|
||||
<QImg v-else :src="src" spinner-color="white" @error="showLetter = true" />
|
||||
<QImg
|
||||
v-else-if="workerId"
|
||||
:src="src"
|
||||
spinner-color="white"
|
||||
@error="showLetter = true"
|
||||
/>
|
||||
<QIcon v-else name="mood" size="xs" />
|
||||
</QAvatar>
|
||||
<div class="description">
|
||||
<slot name="description" v-if="description">
|
||||
|
|
|
@ -61,6 +61,7 @@ const emit = defineEmits([
|
|||
'update:modelValue',
|
||||
'refresh',
|
||||
'clear',
|
||||
'search',
|
||||
'init',
|
||||
'remove',
|
||||
'setUserParams',
|
||||
|
@ -78,7 +79,7 @@ const userParams = ref({});
|
|||
defineExpose({ search, sanitizer, params: userParams });
|
||||
|
||||
onMounted(() => {
|
||||
userParams.value = $props.modelValue ?? {};
|
||||
if (!userParams.value) userParams.value = $props.modelValue ?? {};
|
||||
emit('init', { params: userParams.value });
|
||||
});
|
||||
|
||||
|
@ -104,7 +105,8 @@ watch(
|
|||
|
||||
watch(
|
||||
() => arrayData.store.userParams,
|
||||
(val, oldValue) => (val || oldValue) && setUserParams(val)
|
||||
(val, oldValue) => (val || oldValue) && setUserParams(val),
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
<script setup>
|
||||
import { reactive, useAttrs, onBeforeMount, capitalize } from 'vue';
|
||||
import { ref, reactive, useAttrs, onBeforeMount, capitalize } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { parsePhone } from 'src/filters';
|
||||
import useOpenURL from 'src/composables/useOpenURL';
|
||||
|
||||
const props = defineProps({
|
||||
phoneNumber: { type: [String, Number], default: null },
|
||||
channel: { type: Number, default: null },
|
||||
country: { type: String, default: null },
|
||||
});
|
||||
|
||||
const phone = ref(props.phoneNumber);
|
||||
const config = reactive({
|
||||
sip: { icon: 'phone', href: `sip:${props.phoneNumber}` },
|
||||
'say-simple': {
|
||||
icon: 'vn:saysimple',
|
||||
href: null,
|
||||
url: null,
|
||||
channel: props.channel,
|
||||
},
|
||||
});
|
||||
const type = Object.keys(config).find((key) => key in useAttrs()) || 'sip';
|
||||
|
||||
onBeforeMount(async () => {
|
||||
if (!phone.value) return;
|
||||
let { channel } = config[type];
|
||||
|
||||
if (type === 'say-simple') {
|
||||
|
@ -25,23 +30,28 @@ onBeforeMount(async () => {
|
|||
.data;
|
||||
if (!channel) channel = defaultChannel;
|
||||
|
||||
config[type].href = `${url}?customerIdentity=%2B${parsePhone(
|
||||
props.phoneNumber
|
||||
)}&channelId=${channel}`;
|
||||
phone.value = await parsePhone(props.phoneNumber, props.country.toLowerCase());
|
||||
config[
|
||||
type
|
||||
].url = `${url}?customerIdentity=%2B${phone.value}&channelId=${channel}`;
|
||||
}
|
||||
});
|
||||
|
||||
function handleClick() {
|
||||
if (config[type].url) useOpenURL(config[type].url);
|
||||
else if (config[type].href) window.location.href = config[type].href;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<QBtn
|
||||
v-if="phoneNumber"
|
||||
v-if="phone"
|
||||
flat
|
||||
round
|
||||
:icon="config[type].icon"
|
||||
size="sm"
|
||||
color="primary"
|
||||
padding="none"
|
||||
:href="config[type].href"
|
||||
@click.stop
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ capitalize(type).replace('-', '') }}
|
||||
|
|
|
@ -6,7 +6,6 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useQuasar } from 'quasar';
|
||||
|
||||
import { toDateHourMin } from 'src/filters';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import VnUserLink from 'components/ui/VnUserLink.vue';
|
||||
|
@ -26,9 +25,7 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = useState();
|
||||
const quasar = useQuasar();
|
||||
const currentUser = ref(state.getUser());
|
||||
const newNote = reactive({ text: null, observationTypeFk: null });
|
||||
const observationTypes = ref([]);
|
||||
const vnPaginateRef = ref();
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import { useVnConfirm } from 'src/composables/useVnConfirm';
|
||||
import axios from 'axios';
|
||||
import { ref } from 'vue';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
export async function useCau(res, message) {
|
||||
const { notify } = useNotify();
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const { config, headers, request, status, statusText, data } = res || {};
|
||||
const { params, url, method, signal, headers: confHeaders } = config || {};
|
||||
const { message: resMessage, code, name } = data?.error || {};
|
||||
|
||||
const additionalData = {
|
||||
path: location.hash,
|
||||
message: resMessage,
|
||||
code,
|
||||
request: request?.responseURL,
|
||||
status,
|
||||
name,
|
||||
statusText: statusText,
|
||||
config: {
|
||||
url,
|
||||
method,
|
||||
params,
|
||||
headers: confHeaders,
|
||||
aborted: signal?.aborted,
|
||||
version: headers?.['salix-version'],
|
||||
},
|
||||
};
|
||||
const opts = {
|
||||
actions: [
|
||||
{
|
||||
icon: 'support_agent',
|
||||
color: 'primary',
|
||||
dense: true,
|
||||
flat: false,
|
||||
round: true,
|
||||
handler: async () => {
|
||||
const locale = i18n.global.t;
|
||||
const reason = ref(
|
||||
code == 'ACCESS_DENIED' ? locale('cau.askPrivileges') : ''
|
||||
);
|
||||
openConfirmationModal(
|
||||
locale('cau.title'),
|
||||
locale('cau.subtitle'),
|
||||
async () => {
|
||||
await axios.post('OsTickets/send-to-support', {
|
||||
reason: reason.value,
|
||||
additionalData,
|
||||
});
|
||||
},
|
||||
null,
|
||||
{
|
||||
component: VnInput,
|
||||
props: {
|
||||
modelValue: reason,
|
||||
'onUpdate:modelValue': (val) => (reason.value = val),
|
||||
label: locale('cau.inputLabel'),
|
||||
class: 'full-width',
|
||||
required: true,
|
||||
autofocus: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
notify(message ?? 'globals.error', 'negative', 'error', opts);
|
||||
}
|
|
@ -2,7 +2,7 @@ import { Notify } from 'quasar';
|
|||
import { i18n } from 'src/boot/i18n';
|
||||
|
||||
export default function useNotify() {
|
||||
const notify = (message, type, icon) => {
|
||||
const notify = (message, type, icon, opts = {}) => {
|
||||
const defaultIcons = {
|
||||
warning: 'warning',
|
||||
negative: 'error',
|
||||
|
@ -13,6 +13,7 @@ export default function useNotify() {
|
|||
message: i18n.global.t(message),
|
||||
type: type,
|
||||
icon: icon ? icon : defaultIcons[type],
|
||||
...opts,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -2,8 +2,14 @@ import { useValidator } from 'src/composables/useValidator';
|
|||
|
||||
export function useRequired($attrs) {
|
||||
const { validations } = useValidator();
|
||||
|
||||
const isRequired = Object.keys($attrs).includes('required');
|
||||
const hasRequired = Object.keys($attrs).includes('required');
|
||||
let isRequired = false;
|
||||
if (hasRequired) {
|
||||
const required = $attrs['required'];
|
||||
if (typeof required === 'boolean') {
|
||||
isRequired = required;
|
||||
}
|
||||
}
|
||||
const requiredFieldRule = (val) => validations().required(isRequired, val);
|
||||
|
||||
return {
|
||||
|
|
|
@ -20,7 +20,7 @@ export function useRole() {
|
|||
|
||||
function hasAny(roles) {
|
||||
const roleStore = state.getRoles();
|
||||
|
||||
if (typeof roles === 'string') roles = [roles];
|
||||
for (const role of roles) {
|
||||
if (roleStore.value.indexOf(role) !== -1) return true;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
import { h } from 'vue';
|
||||
import { Dialog } from 'quasar';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
export function useVnConfirm() {
|
||||
const quasar = useQuasar();
|
||||
|
||||
const openConfirmationModal = (title, message, promise, successFn) => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
const openConfirmationModal = (
|
||||
title,
|
||||
message,
|
||||
promise,
|
||||
successFn,
|
||||
customHTML = {}
|
||||
) => {
|
||||
const { component, props } = customHTML;
|
||||
Dialog.create({
|
||||
component: h(
|
||||
VnConfirm,
|
||||
{
|
||||
title: title,
|
||||
message: message,
|
||||
promise: promise,
|
||||
},
|
||||
})
|
||||
.onOk(async () => {
|
||||
if (successFn) successFn();
|
||||
});
|
||||
{ customHTML: () => h(component, props) }
|
||||
),
|
||||
}).onOk(async () => {
|
||||
if (successFn) successFn();
|
||||
});
|
||||
};
|
||||
|
||||
return { openConfirmationModal };
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// parsing JSON safely
|
||||
function parseJSON(str, fallback) {
|
||||
try {
|
||||
return JSON.parse(str ?? '{}');
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
export default function (phone, prefix = 34) {
|
||||
if (phone.startsWith('+')) {
|
||||
return `${phone.slice(1)}`;
|
||||
}
|
||||
if (phone.startsWith('00')) {
|
||||
return `${phone.slice(2)}`;
|
||||
}
|
||||
if (phone.startsWith(prefix) && phone.length === prefix.length + 9) {
|
||||
return `${prefix}${phone.slice(prefix.length)}`;
|
||||
import axios from 'axios';
|
||||
|
||||
export default async function parsePhone(phone, country) {
|
||||
if (!phone) return;
|
||||
if (phone.startsWith('+')) return `${phone.slice(1)}`;
|
||||
if (phone.startsWith('00')) return `${phone.slice(2)}`;
|
||||
|
||||
let prefix;
|
||||
try {
|
||||
prefix = (await axios.get(`Prefixes/${country.toLowerCase()}`)).data?.prefix;
|
||||
} catch (e) {
|
||||
prefix = (await axios.get('PbxConfigs/findOne')).data?.defaultPrefix;
|
||||
}
|
||||
prefix = prefix.replace(/^0+/, '');
|
||||
|
||||
if (phone.startsWith(prefix)) return phone;
|
||||
return `${prefix}${phone}`;
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ globals:
|
|||
small: Small
|
||||
medium: Medium
|
||||
big: Big
|
||||
email: Email
|
||||
pageTitles:
|
||||
logIn: Login
|
||||
addressEdit: Update address
|
||||
|
@ -329,8 +330,10 @@ globals:
|
|||
email: Email
|
||||
SSN: SSN
|
||||
fi: FI
|
||||
packing: ITP
|
||||
myTeam: My team
|
||||
departmentFk: Department
|
||||
countryFk: Country
|
||||
changePass: Change password
|
||||
deleteConfirmTitle: Delete selected elements
|
||||
changeState: Change state
|
||||
|
@ -368,6 +371,11 @@ resetPassword:
|
|||
repeatPassword: Repeat password
|
||||
passwordNotMatch: Passwords don't match
|
||||
passwordChanged: Password changed
|
||||
cau:
|
||||
title: Send cau
|
||||
subtitle: By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.
|
||||
inputLabel: Explain why this error should not appear
|
||||
askPrivileges: Ask for privileges
|
||||
entry:
|
||||
list:
|
||||
newEntry: New entry
|
||||
|
@ -397,8 +405,8 @@ entry:
|
|||
buys: Buys
|
||||
stickers: Stickers
|
||||
package: Package
|
||||
packing: Packing
|
||||
grouping: Grouping
|
||||
packing: Pack.
|
||||
grouping: Group.
|
||||
buyingValue: Buying value
|
||||
import: Import
|
||||
pvp: PVP
|
||||
|
@ -768,7 +776,7 @@ travel:
|
|||
thermographs: Thermographs
|
||||
hb: HB
|
||||
basicData:
|
||||
daysInForward: Days in forward
|
||||
daysInForward: Automatic movement (Raid)
|
||||
isRaid: Raid
|
||||
thermographs:
|
||||
temperature: Temperature
|
||||
|
|
|
@ -131,6 +131,7 @@ globals:
|
|||
small: Pequeño/a
|
||||
medium: Mediano/a
|
||||
big: Grande
|
||||
email: Correo
|
||||
pageTitles:
|
||||
logIn: Inicio de sesión
|
||||
addressEdit: Modificar consignatario
|
||||
|
@ -335,6 +336,8 @@ globals:
|
|||
SSN: NSS
|
||||
fi: NIF
|
||||
myTeam: Mi equipo
|
||||
packing: ITP
|
||||
countryFk: País
|
||||
changePass: Cambiar contraseña
|
||||
deleteConfirmTitle: Eliminar los elementos seleccionados
|
||||
changeState: Cambiar estado
|
||||
|
@ -370,6 +373,11 @@ resetPassword:
|
|||
repeatPassword: Repetir contraseña
|
||||
passwordNotMatch: Las contraseñas no coinciden
|
||||
passwordChanged: Contraseña cambiada
|
||||
cau:
|
||||
title: Enviar cau
|
||||
subtitle: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
|
||||
inputLabel: Explique el motivo por el que no deberia aparecer este fallo
|
||||
askPrivileges: Solicitar permisos
|
||||
entry:
|
||||
list:
|
||||
newEntry: Nueva entrada
|
||||
|
@ -400,8 +408,8 @@ entry:
|
|||
buys: Compras
|
||||
stickers: Etiquetas
|
||||
package: Embalaje
|
||||
packing: Packing
|
||||
grouping: Grouping
|
||||
packing: Pack.
|
||||
grouping: Group.
|
||||
buyingValue: Coste
|
||||
import: Importe
|
||||
pvp: PVP
|
||||
|
@ -491,7 +499,7 @@ invoiceOut:
|
|||
ticketList: Listado de tickets
|
||||
summary:
|
||||
issued: Fecha
|
||||
dued: Vencimiento
|
||||
dued: Fecha límite
|
||||
booked: Contabilizada
|
||||
taxBreakdown: Desglose impositivo
|
||||
taxableBase: Base imp.
|
||||
|
@ -762,7 +770,7 @@ travel:
|
|||
thermographs: Termógrafos
|
||||
hb: HB
|
||||
basicData:
|
||||
daysInForward: Días redada
|
||||
daysInForward: Desplazamiento automatico (redada)
|
||||
isRaid: Redada
|
||||
thermographs:
|
||||
temperature: Temperatura
|
||||
|
|
|
@ -1,50 +1,10 @@
|
|||
<script setup>
|
||||
import { useQuasar } from 'quasar';
|
||||
import Navbar from 'src/components/NavBar.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import routes from 'src/router/modules';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
const quasar = useQuasar();
|
||||
|
||||
onMounted(() => {
|
||||
let isNotified = false;
|
||||
|
||||
const router = useRouter();
|
||||
const keyBindingMap = routes
|
||||
.filter((route) => route.meta.keyBinding)
|
||||
.reduce((map, route) => {
|
||||
map['Key' + route.meta.keyBinding.toUpperCase()] = route.path;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
const { ctrlKey, altKey, code } = event;
|
||||
|
||||
if (ctrlKey && altKey && keyBindingMap[code] && !isNotified) {
|
||||
event.preventDefault();
|
||||
router.push(keyBindingMap[code]);
|
||||
isNotified = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (event) => {
|
||||
const { ctrlKey, altKey } = event;
|
||||
|
||||
if (!ctrlKey || !altKey) {
|
||||
isNotified = false;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QLayout view="hHh LpR fFf" v-shortcut>
|
||||
<Navbar />
|
||||
<RouterView></RouterView>
|
||||
<QFooter v-if="quasar.platform.is.mobile"></QFooter>
|
||||
<QFooter v-if="$q.platform.is.mobile"></QFooter>
|
||||
</QLayout>
|
||||
</template>
|
||||
|
|
|
@ -31,7 +31,6 @@ const rolesOptions = ref([]);
|
|||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:search-button="true"
|
||||
:hidden-tags="['search']"
|
||||
:redirect="false"
|
||||
search-url="table"
|
||||
>
|
||||
|
|
|
@ -7,6 +7,7 @@ import AccountSummary from './Card/AccountSummary.vue';
|
|||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import AccountFilter from './AccountFilter.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const tableRef = ref();
|
||||
|
@ -22,10 +23,27 @@ const columns = computed(() => [
|
|||
field: 'id',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'name',
|
||||
label: t('Name'),
|
||||
component: 'input',
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'roleFk',
|
||||
label: t('role'),
|
||||
label: t('Role'),
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'VnRoles',
|
||||
optionValue: 'id',
|
||||
optionLabel: 'name',
|
||||
},
|
||||
columnFilter: {
|
||||
component: 'select',
|
||||
name: 'roleFk',
|
||||
|
@ -35,7 +53,11 @@ const columns = computed(() => [
|
|||
optionLabel: 'name',
|
||||
},
|
||||
},
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
format: ({ role }, dashIfEmpty) => dashIfEmpty(role?.name),
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -51,20 +73,32 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'name',
|
||||
label: t('Name'),
|
||||
name: 'email',
|
||||
label: t('Email'),
|
||||
component: 'input',
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'email',
|
||||
label: t('email'),
|
||||
component: 'input',
|
||||
name: 'password',
|
||||
label: t('Password'),
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
attrs: {},
|
||||
required: true,
|
||||
visible: false,
|
||||
},
|
||||
|
||||
{
|
||||
align: 'left',
|
||||
name: 'active',
|
||||
label: t('Active'),
|
||||
component: 'checkbox',
|
||||
create: true,
|
||||
visible: false,
|
||||
},
|
||||
|
@ -101,7 +135,6 @@ const exprBuilder = (param, value) => {
|
|||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSearchbar
|
||||
data-key="AccountList"
|
||||
|
@ -119,6 +152,12 @@ const exprBuilder = (param, value) => {
|
|||
ref="tableRef"
|
||||
data-key="AccountList"
|
||||
url="VnUsers/preview"
|
||||
:create="{
|
||||
urlCreate: 'VnUsers',
|
||||
title: t('Create user'),
|
||||
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||
formInitialData: {},
|
||||
}"
|
||||
:filter="filter"
|
||||
order="id DESC"
|
||||
:columns="columns"
|
||||
|
@ -127,7 +166,19 @@ const exprBuilder = (param, value) => {
|
|||
:use-model="true"
|
||||
:right-search="false"
|
||||
auto-load
|
||||
/>
|
||||
>
|
||||
<template #more-create-dialog="{ data }">
|
||||
<QCardSection>
|
||||
<VnInput
|
||||
:label="t('Password')"
|
||||
v-model="data.password"
|
||||
type="password"
|
||||
:required="true"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</QCardSection>
|
||||
</template>
|
||||
</VnTable>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
|
@ -135,4 +186,7 @@ const exprBuilder = (param, value) => {
|
|||
Id: Id
|
||||
Nickname: Nickname
|
||||
Name: Nombre
|
||||
Password: Contraseña
|
||||
Active: Activo
|
||||
Role: Rol
|
||||
</i18n>
|
||||
|
|
|
@ -37,11 +37,7 @@ onBeforeMount(() => {
|
|||
@on-fetch="(data) => (rolesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:search-button="true"
|
||||
:hidden-tags="['search']"
|
||||
>
|
||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ t(`acls.aclFilter.${tag.label}`) }}: </strong>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useAcl } from 'src/composables/useAcl';
|
|||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||
import VnChangePassword from 'src/components/common/VnChangePassword.vue';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
const $props = defineProps({
|
||||
hasAccount: {
|
||||
|
@ -21,7 +21,7 @@ const { t } = useI18n();
|
|||
const { hasAccount } = toRefs($props);
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const route = useRoute();
|
||||
const { notify } = useNotify();
|
||||
const { notify } = useQuasar();
|
||||
const account = computed(() => useArrayData('AccountId').store.data[0]);
|
||||
account.value.hasAccount = hasAccount.value;
|
||||
const entityId = computed(() => +route.params.id);
|
||||
|
|
|
@ -13,12 +13,7 @@ const props = defineProps({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:search-button="true"
|
||||
:hidden-tags="['search']"
|
||||
:redirect="false"
|
||||
>
|
||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true" :redirect="false">
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ t(`role.${tag.label}`) }}: </strong>
|
||||
|
|
|
@ -8,7 +8,7 @@ import FormModel from 'components/FormModel.vue';
|
|||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnAvatar from 'src/components/ui/VnAvatar.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
import { getDifferences, getUpdatedValues } from 'src/filters';
|
||||
|
||||
const route = useRoute();
|
||||
|
@ -16,7 +16,6 @@ const { t } = useI18n();
|
|||
|
||||
const businessTypes = ref([]);
|
||||
const contactChannels = ref([]);
|
||||
const title = ref();
|
||||
const handleSalesModelValue = (val) => ({
|
||||
or: [
|
||||
{ id: val },
|
||||
|
@ -117,41 +116,17 @@ function onBeforeSave(formData, originalData) {
|
|||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
url="Workers/search"
|
||||
v-model="data.salesPersonFk"
|
||||
<VnSelectWorker
|
||||
:label="t('customer.summary.salesPerson')"
|
||||
v-model="data.salesPersonFk"
|
||||
:params="{
|
||||
departmentCodes: ['VT', 'shopping'],
|
||||
}"
|
||||
:fields="['id', 'nickname']"
|
||||
sort-by="nickname ASC"
|
||||
option-label="nickname"
|
||||
option-value="id"
|
||||
:has-avatar="true"
|
||||
:rules="validate('client.salesPersonFk')"
|
||||
:expr-builder="exprBuilder"
|
||||
emit-value
|
||||
auto-load
|
||||
>
|
||||
<template #prepend>
|
||||
<VnAvatar
|
||||
:worker-id="data.salesPersonFk"
|
||||
color="primary"
|
||||
:title="title"
|
||||
/>
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
|
||||
<QItemLabel caption
|
||||
>{{ scope.opt?.nickname }},
|
||||
{{ scope.opt?.code }}</QItemLabel
|
||||
>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
/>
|
||||
<VnSelect
|
||||
v-model="data.contactChannelFk"
|
||||
:options="contactChannels"
|
||||
|
@ -169,7 +144,6 @@ function onBeforeSave(formData, originalData) {
|
|||
url="Clients"
|
||||
:input-debounce="0"
|
||||
:label="t('customer.basicData.previousClient')"
|
||||
:options="clients"
|
||||
:rules="validate('client.transferorFk')"
|
||||
emit-value
|
||||
map-options
|
||||
|
|
|
@ -67,6 +67,7 @@ function handleLocation(data, location) {
|
|||
option-label="vat"
|
||||
option-value="id"
|
||||
v-model="data.sageTaxTypeFk"
|
||||
data-cy="sageTaxTypeFk"
|
||||
:required="data.isTaxDataChecked"
|
||||
/>
|
||||
<VnSelect
|
||||
|
@ -75,6 +76,7 @@ function handleLocation(data, location) {
|
|||
hide-selected
|
||||
option-label="transaction"
|
||||
option-value="id"
|
||||
data-cy="sageTransactionTypeFk"
|
||||
v-model="data.sageTransactionTypeFk"
|
||||
:required="data.isTaxDataChecked"
|
||||
>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<script setup>
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||
|
||||
import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import { getUrl } from 'src/composables/getUrl';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
||||
import VnLinkMail from 'src/components/ui/VnLinkMail.vue';
|
||||
|
@ -95,13 +94,14 @@ const sumRisk = ({ clientRisks }) => {
|
|||
:phone-number="entity.mobile"
|
||||
:channel="entity.country?.saySimpleCountry?.channel"
|
||||
class="q-ml-xs"
|
||||
:country="entity.country?.code"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :value="entity.email" copy
|
||||
><template #label>
|
||||
{{ t('globals.params.email') }}
|
||||
<VnLinkMail email="entity.email"></VnLinkMail> </template
|
||||
<VnLinkMail :email="entity.email"></VnLinkMail> </template
|
||||
></VnLv>
|
||||
<VnLv
|
||||
:label="t('customer.summary.salesPerson')"
|
||||
|
|
|
@ -29,7 +29,8 @@ async function hasCustomerRole() {
|
|||
:filter="filter"
|
||||
model="customer"
|
||||
:mapper="
|
||||
({ active, name, email }) => {
|
||||
({ account }) => {
|
||||
const { name, email, active } = account;
|
||||
return {
|
||||
active,
|
||||
name,
|
||||
|
@ -42,13 +43,13 @@ async function hasCustomerRole() {
|
|||
>
|
||||
<template #form="{ data, validate }">
|
||||
<QCheckbox :label="t('Enable web access')" v-model="data.account.active" />
|
||||
<VnInput :label="t('User')" clearable v-model="data.name" />
|
||||
<VnInput :label="t('User')" clearable v-model="data.account.name" />
|
||||
<VnInput
|
||||
:label="t('Recovery email')"
|
||||
:rules="validate('client.email')"
|
||||
clearable
|
||||
type="email"
|
||||
v-model="data.email"
|
||||
v-model="data.account.email"
|
||||
class="q-mt-sm"
|
||||
:info="t('This email is used for user to regain access their account')"
|
||||
/>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
defineProps({
|
||||
|
@ -65,19 +66,14 @@ const exprBuilder = (param, value) => {
|
|||
</QItem>
|
||||
<QItem class="q-mb-sm">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
url="Workers/search"
|
||||
<VnSelectWorker
|
||||
:label="t('Salesperson')"
|
||||
v-model="params.salesPersonFk"
|
||||
:params="{
|
||||
departmentCodes: ['VT'],
|
||||
}"
|
||||
auto-load
|
||||
:label="t('Salesperson')"
|
||||
:expr-builder="exprBuilder"
|
||||
v-model="params.salesPersonFk"
|
||||
@update:model-value="searchFn()"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
sort-by="nickname ASC"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
|
@ -86,18 +82,7 @@ const exprBuilder = (param, value) => {
|
|||
outlined
|
||||
rounded
|
||||
:input-debounce="0"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }},{{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template></VnSelect
|
||||
>
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mb-sm">
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { ref, computed, markRaw } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnLocation from 'src/components/common/VnLocation.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
|
@ -12,6 +11,7 @@ import RightMenu from 'src/components/common/RightMenu.vue';
|
|||
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
||||
import { toDate } from 'src/filters';
|
||||
import CustomerFilter from './CustomerFilter.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
@ -421,40 +421,17 @@ function handleLocation(data, location) {
|
|||
auto-load
|
||||
>
|
||||
<template #more-create-dialog="{ data }">
|
||||
<VnSelect
|
||||
url="Workers/search"
|
||||
v-model="data.salesPersonFk"
|
||||
<VnSelectWorker
|
||||
:label="t('customer.summary.salesPerson')"
|
||||
v-model="data.salesPersonFk"
|
||||
:params="{
|
||||
departmentCodes: ['VT', 'shopping'],
|
||||
}"
|
||||
:fields="['id', 'nickname', 'code']"
|
||||
sort-by="nickname ASC"
|
||||
option-label="nickname"
|
||||
option-value="id"
|
||||
:has-avatar="true"
|
||||
:id-value="data.salesPersonFk"
|
||||
emit-value
|
||||
auto-load
|
||||
>
|
||||
<template #prepend>
|
||||
<VnAvatar
|
||||
:worker-id="data.salesPersonFk"
|
||||
color="primary"
|
||||
:title="title"
|
||||
/>
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
|
||||
<QItemLabel caption
|
||||
>{{ scope.opt?.nickname }},
|
||||
{{ scope.opt?.code }}</QItemLabel
|
||||
>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
|
||||
/>
|
||||
<VnLocation
|
||||
:acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"
|
||||
v-model="data.location"
|
||||
|
|
|
@ -18,8 +18,6 @@ const router = useRouter();
|
|||
|
||||
const formInitialData = reactive({ isDefaultAddress: false });
|
||||
|
||||
const urlCreate = ref('');
|
||||
|
||||
const agencyModes = ref([]);
|
||||
const incoterms = ref([]);
|
||||
const customsAgents = ref([]);
|
||||
|
@ -40,13 +38,18 @@ function handleLocation(data, location) {
|
|||
data.countryFk = countryFk;
|
||||
}
|
||||
|
||||
function onAgentCreated(requestResponse, data) {
|
||||
customsAgents.value.push(requestResponse);
|
||||
data.customsAgentFk = requestResponse.id;
|
||||
function onAgentCreated({ id, fiscalName }, data) {
|
||||
customsAgents.value.push({ id, fiscalName });
|
||||
data.customsAgentFk = id;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (customsAgents = data)"
|
||||
auto-load
|
||||
url="CustomsAgents"
|
||||
/>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (agencyModes = data)"
|
||||
auto-load
|
||||
|
@ -57,7 +60,7 @@ function onAgentCreated(requestResponse, data) {
|
|||
<FormModel
|
||||
:form-initial-data="formInitialData"
|
||||
:observe-form-changes="false"
|
||||
:url-create="urlCreate"
|
||||
:url-create="`Clients/${route.params.id}/createAddress`"
|
||||
@on-data-saved="toCustomerAddress()"
|
||||
model="client"
|
||||
>
|
||||
|
@ -141,8 +144,7 @@ function onAgentCreated(requestResponse, data) {
|
|||
<template #form>
|
||||
<CustomerNewCustomsAgent
|
||||
@on-data-saved="
|
||||
(_, requestResponse) =>
|
||||
onAgentCreated(requestResponse, data)
|
||||
(requestResponse) => onAgentCreated(requestResponse, data)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -12,6 +12,7 @@ import VnInput from 'src/components/common/VnInput.vue';
|
|||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
|
||||
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
@ -144,7 +145,6 @@ function handleLocation(data, location) {
|
|||
:url="`Addresses/${route.params.addressId}`"
|
||||
@on-data-saved="onDataSaved()"
|
||||
auto-load
|
||||
model="customer"
|
||||
>
|
||||
<template #moreActions>
|
||||
<QBtn
|
||||
|
@ -220,31 +220,35 @@ function handleLocation(data, location) {
|
|||
</div>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<div class="col">
|
||||
<VnSelect
|
||||
:label="t('Incoterms')"
|
||||
:options="incoterms"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="code"
|
||||
v-model="data.incotermsFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectDialog
|
||||
:label="t('Customs agent')"
|
||||
:options="customsAgents"
|
||||
hide-selected
|
||||
option-label="fiscalName"
|
||||
option-value="id"
|
||||
v-model="data.customsAgentFk"
|
||||
:tooltip="t('New customs agent')"
|
||||
>
|
||||
<template #form>
|
||||
<CustomerNewCustomsAgent />
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Incoterms')"
|
||||
:options="incoterms"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="code"
|
||||
v-model="data.incotermsFk"
|
||||
/>
|
||||
<VnSelectDialog
|
||||
:label="t('Customs agent')"
|
||||
:options="customsAgents"
|
||||
hide-selected
|
||||
option-label="fiscalName"
|
||||
option-value="id"
|
||||
v-model="data.customsAgentFk"
|
||||
:tooltip="t('New customs agent')"
|
||||
>
|
||||
<template #form>
|
||||
<CustomerNewCustomsAgent />
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInputNumber
|
||||
:label="t('Longitude')"
|
||||
clearable
|
||||
v-model="data.longitude"
|
||||
/>
|
||||
<VnInputNumber :label="t('Latitude')" clearable v-model="data.latitude" />
|
||||
</VnRow>
|
||||
<h4 class="q-mb-xs">{{ t('Notes') }}</h4>
|
||||
<VnRow
|
||||
|
@ -322,4 +326,6 @@ es:
|
|||
Description: Descripción
|
||||
Add note: Añadir nota
|
||||
Remove note: Eliminar nota
|
||||
Longitude: Longitud
|
||||
Latitude: Latitud
|
||||
</i18n>
|
||||
|
|
|
@ -6,6 +6,7 @@ import FormModel from 'components/FormModel.vue';
|
|||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -48,14 +49,9 @@ const { t } = useI18n();
|
|||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
<VnSelectWorker
|
||||
:label="t('department.bossDepartment')"
|
||||
v-model="data.workerFk"
|
||||
url="Workers/search"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
map-options
|
||||
:rules="validate('department.workerFk')"
|
||||
/>
|
||||
<VnSelect
|
||||
|
|
|
@ -83,7 +83,7 @@ const { openConfirmationModal } = useVnConfirm();
|
|||
</template>
|
||||
<template #body="{ entity }">
|
||||
<VnLv :label="t('department.chat')" :value="entity.chatName" />
|
||||
<VnLv :label="t('department.email')" :value="entity.notificationEmail" copy />
|
||||
<VnLv :label="t('globals.email')" :value="entity.notificationEmail" copy />
|
||||
<VnLv
|
||||
:label="t('department.selfConsumptionCustomer')"
|
||||
:value="entity.client?.name"
|
||||
|
|
|
@ -58,7 +58,7 @@ onMounted(async () => {
|
|||
dash
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('department.email')"
|
||||
:label="t('globals.email')"
|
||||
:value="department.notificationEmail"
|
||||
dash
|
||||
/>
|
||||
|
|
|
@ -52,7 +52,9 @@ const columns = computed(() => [
|
|||
label: t('invoiceOutList.tableVisibleColumns.id'),
|
||||
chip: { condition: () => true },
|
||||
isId: true,
|
||||
columnFilter: { name: 'search' },
|
||||
columnFilter: {
|
||||
name: 'id',
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -84,8 +86,15 @@ const columns = computed(() => [
|
|||
label: t('globals.client'),
|
||||
cardVisible: true,
|
||||
component: 'select',
|
||||
attrs: { url: 'Clients', fields: ['id', 'name'] },
|
||||
columnField: { component: null },
|
||||
attrs: {
|
||||
url: 'Clients',
|
||||
fields: ['id', 'socialName'],
|
||||
optionLabel: 'socialName',
|
||||
optionValue: 'id',
|
||||
},
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
|
|
|
@ -11,7 +11,7 @@ invoiceOutList:
|
|||
ref: Referencia
|
||||
issued: Fecha emisión
|
||||
created: F. creación
|
||||
dueDate: F. máxima
|
||||
dueDate: Fecha vencimiento
|
||||
invoiceOutSerial: Serial
|
||||
ticket: Ticket
|
||||
taxArea: Area
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
import { ref, nextTick } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
|
||||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const itemBarcodeRef = ref(null);
|
||||
|
||||
|
@ -23,6 +26,24 @@ const focusLastInput = () => {
|
|||
if (lastInput) lastInput.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const removeRow = (row) => {
|
||||
itemBarcodeRef.value.remove([row]);
|
||||
};
|
||||
|
||||
const submit = async (rows) => {
|
||||
const params = rows[rows.length - 1];
|
||||
let { data } = await axios.get('ItemBarcodes');
|
||||
const code = params.code;
|
||||
|
||||
if (data.some((codes) => codes.code === code)) {
|
||||
notify(t('Codes can not be repeated'), 'negative');
|
||||
itemBarcodeRef.value.reset();
|
||||
return;
|
||||
}
|
||||
await axios.patch(`ItemBarcodes`, params);
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="full-width flex justify-center">
|
||||
|
@ -39,6 +60,7 @@ const focusLastInput = () => {
|
|||
ref="itemBarcodeRef"
|
||||
url="ItemBarcodes"
|
||||
auto-load
|
||||
:save-fn="submit"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard class="q-px-lg q-py-md">
|
||||
|
@ -54,7 +76,7 @@ const focusLastInput = () => {
|
|||
focusable-input
|
||||
/>
|
||||
<QIcon
|
||||
@click="itemBarcodeRef.remove([row])"
|
||||
@click="removeRow(row)"
|
||||
class="cursor-pointer q-ml-md"
|
||||
color="primary"
|
||||
name="delete"
|
||||
|
@ -89,4 +111,5 @@ es:
|
|||
Code: Código
|
||||
Remove barcode: Quitar código de barras
|
||||
Add barcode: Añadir código de barras
|
||||
Codes can not be repeated: Los códigos no puden ser repetidos
|
||||
</i18n>
|
||||
|
|
|
@ -70,6 +70,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
option-label="name"
|
||||
hide-selected
|
||||
map-options
|
||||
required
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, reactive, computed } from 'vue';
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { cloneItem } from 'src/pages/Item/composables/cloneItem';
|
|||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ const $props = defineProps({
|
|||
default: null,
|
||||
},
|
||||
saleFk: {
|
||||
type: Number,
|
||||
type: [Number, String],
|
||||
default: null,
|
||||
},
|
||||
warehouseFk: {
|
||||
|
@ -61,7 +61,7 @@ onMounted(async () => {
|
|||
const data = ref(useCardDescription());
|
||||
const setData = async (entity) => {
|
||||
if (!entity) return;
|
||||
data.value = useCardDescription(entity.name, entity.id);
|
||||
data.value = useCardDescription(entity?.name, entity?.id);
|
||||
await updateStock();
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const $props = defineProps({
|
|||
default: null,
|
||||
},
|
||||
entityId: {
|
||||
type: String,
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
showEditButton: {
|
||||
|
@ -67,7 +67,7 @@ const handlePhotoUpdated = (evt = false) => {
|
|||
|
||||
<template>
|
||||
<div class="relative-position">
|
||||
<VnImg ref="image" :id="$props.entityId" zoom-resolution="1600x900">
|
||||
<VnImg ref="image" :id="parseInt($props.entityId)" zoom-resolution="1600x900">
|
||||
<template #error>
|
||||
<div class="absolute-full picture text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, computed, onUnmounted, reactive, ref, nextTick, watch } from 'vue';
|
||||
import { onMounted, computed, reactive, ref, nextTick, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
|
@ -12,20 +12,18 @@ import FetchData from 'components/FetchData.vue';
|
|||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toDateFormat } from 'src/filters/date.js';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { date } from 'quasar';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import axios from 'axios';
|
||||
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const stateStore = useStateStore();
|
||||
const state = useState();
|
||||
|
||||
const user = state.getUser();
|
||||
const today = ref(Date.vnNew());
|
||||
const warehousesOptions = ref([]);
|
||||
|
@ -145,8 +143,6 @@ onMounted(async () => {
|
|||
await updateWarehouse(warehouseFk.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value.params.id,
|
||||
(newId) => {
|
||||
|
@ -205,8 +201,8 @@ async function updateWarehouse(warehouseFk) {
|
|||
auto-load
|
||||
@on-fetch="(data) => (warehousesOptions = data)"
|
||||
/>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#st-data">
|
||||
<VnSubToolbar class="q-mb-md">
|
||||
<template #st-data>
|
||||
<div class="row">
|
||||
<VnSelect
|
||||
:label="t('itemDiary.warehouse')"
|
||||
|
@ -235,9 +231,8 @@ async function updateWarehouse(warehouseFk) {
|
|||
@update:model-value="fetchItemBalances"
|
||||
/>
|
||||
</div>
|
||||
</Teleport>
|
||||
<Teleport to="#st-actions"> </Teleport>
|
||||
</template>
|
||||
</template>
|
||||
</VnSubToolbar>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<QTable
|
||||
:rows="itemBalances"
|
||||
|
|
|
@ -1,21 +1,30 @@
|
|||
<script setup>
|
||||
import { onMounted, computed, onUnmounted, ref, watch } from 'vue';
|
||||
import { onMounted, computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { dateRange } from 'src/filters';
|
||||
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toDateTimeFormat } from 'src/filters/date.js';
|
||||
import VnDateBadge from 'src/components/common/VnDateBadge.vue';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { toCurrency } from 'filters/index';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import axios from 'axios';
|
||||
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
const from = ref();
|
||||
const to = ref();
|
||||
const hideInventory = ref(true);
|
||||
const inventorySupplierFk = ref();
|
||||
|
||||
async function getInventorySupplier() {
|
||||
inventorySupplierFk.value = (
|
||||
await axios.get(`InventoryConfigs`)
|
||||
)?.data[0]?.supplierFk;
|
||||
}
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
|
@ -36,25 +45,27 @@ const exprBuilder = (param, value) => {
|
|||
}
|
||||
};
|
||||
|
||||
const from = ref();
|
||||
const to = ref();
|
||||
const where = {
|
||||
itemFk: route.params.id,
|
||||
};
|
||||
|
||||
if (hideInventory.value) {
|
||||
where.supplierFk = { neq: inventorySupplierFk };
|
||||
}
|
||||
|
||||
const arrayData = useArrayData('ItemLastEntries', {
|
||||
url: 'Items/lastEntriesFilter',
|
||||
order: ['landed DESC', 'buyFk DESC'],
|
||||
exprBuilder: exprBuilder,
|
||||
userFilter: {
|
||||
where: {
|
||||
itemFk: route.params.id,
|
||||
},
|
||||
where: where,
|
||||
},
|
||||
});
|
||||
|
||||
const itemLastEntries = ref([]);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: t('lastEntries.ig'),
|
||||
label: 'Nv',
|
||||
name: 'ig',
|
||||
align: 'center',
|
||||
},
|
||||
|
@ -62,33 +73,38 @@ const columns = computed(() => [
|
|||
label: t('itemDiary.warehouse'),
|
||||
name: 'warehouse',
|
||||
field: 'warehouse',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.landed'),
|
||||
name: 'id',
|
||||
name: 'date',
|
||||
field: 'landed',
|
||||
align: 'left',
|
||||
format: (val) => toDateTimeFormat(val),
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.entry'),
|
||||
name: 'entry',
|
||||
field: 'stateName',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.pvp'),
|
||||
name: 'pvp',
|
||||
field: 'reference',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3),
|
||||
},
|
||||
|
||||
{
|
||||
label: t('lastEntries.printedStickers'),
|
||||
name: 'printedStickers',
|
||||
field: 'printedStickers',
|
||||
align: 'center',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.label'),
|
||||
name: 'label',
|
||||
name: 'stickers',
|
||||
field: 'stickers',
|
||||
align: 'center',
|
||||
format: (val) => dashIfEmpty(val),
|
||||
|
@ -96,11 +112,13 @@ const columns = computed(() => [
|
|||
{
|
||||
label: t('shelvings.packing'),
|
||||
name: 'packing',
|
||||
field: 'packing',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.grouping'),
|
||||
name: 'grouping',
|
||||
field: 'grouping',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
|
@ -111,18 +129,19 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
label: t('lastEntries.quantity'),
|
||||
name: 'stems',
|
||||
name: 'quantity',
|
||||
field: 'quantity',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.cost'),
|
||||
name: 'cost',
|
||||
align: 'left',
|
||||
field: 'cost',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: t('lastEntries.kg'),
|
||||
name: 'stems',
|
||||
label: 'Kg',
|
||||
name: 'weight',
|
||||
field: 'weight',
|
||||
align: 'center',
|
||||
},
|
||||
|
@ -134,9 +153,9 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
label: t('lastEntries.supplier'),
|
||||
name: 'stems',
|
||||
name: 'supplier',
|
||||
field: 'supplier',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -160,11 +179,18 @@ const updateFilter = async () => {
|
|||
else if (from.value && !to.value) filter = { gte: from.value };
|
||||
else if (from.value && to.value) filter = { between: [from.value, to.value] };
|
||||
|
||||
arrayData.store.userFilter.where.landed = filter;
|
||||
const userFilter = arrayData.store.userFilter.where;
|
||||
|
||||
userFilter.landed = filter;
|
||||
if (hideInventory.value) userFilter.supplierFk = { neq: inventorySupplierFk };
|
||||
else delete userFilter.supplierFk;
|
||||
|
||||
await fetchItemLastEntries();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await getInventorySupplier();
|
||||
|
||||
const _from = Date.vnNew();
|
||||
_from.setDate(_from.getDate() - 75);
|
||||
from.value = getDate(_from, 'from');
|
||||
|
@ -174,16 +200,13 @@ onMounted(async () => {
|
|||
|
||||
updateFilter();
|
||||
|
||||
watch([from, to], ([nFrom, nTo], [oFrom, oTo]) => {
|
||||
watch([from, to, hideInventory], ([nFrom, nTo], [oFrom, oTo]) => {
|
||||
if (nFrom && nFrom != oFrom) nFrom = getDate(new Date(nFrom), 'from');
|
||||
if (nTo && nTo != oTo) nTo = getDate(new Date(nTo), 'to');
|
||||
updateFilter();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSubToolbar>
|
||||
<template #st-data>
|
||||
|
@ -192,27 +215,45 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
dense
|
||||
v-model="from"
|
||||
class="q-mr-lg"
|
||||
data-cy="from"
|
||||
/>
|
||||
<VnInputDate
|
||||
:label="t('lastEntries.to')"
|
||||
v-model="to"
|
||||
dense
|
||||
class="q-mr-lg"
|
||||
data-cy="to"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('Hide inventory supplier')"
|
||||
v-model="hideInventory"
|
||||
dense
|
||||
class="q-mr-lg"
|
||||
data-cy="hideInventory"
|
||||
/>
|
||||
<VnInputDate :label="t('lastEntries.to')" dense v-model="to" />
|
||||
</template>
|
||||
</VnSubToolbar>
|
||||
<QPage class="column items-center q-pa-xd">
|
||||
<QTable
|
||||
:rows="itemLastEntries"
|
||||
:columns="columns"
|
||||
class="full-width q-mt-md"
|
||||
class="table full-width q-mt-md"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
>
|
||||
<template #body-cell-ig="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QCheckbox
|
||||
v-model="row.isIgnored"
|
||||
:disable="true"
|
||||
:false-value="0"
|
||||
:true-value="1"
|
||||
<QTd class="text-center">
|
||||
<QIcon
|
||||
:name="row.isIgnored ? 'check_box' : 'check_box_outline_blank'"
|
||||
style="color: var(--vn-label-color)"
|
||||
size="sm"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-date="{ row }">
|
||||
<QTd class="text-center">
|
||||
<VnDateBadge :date="row.landed" />
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-entry="{ row }">
|
||||
<QTd @click.stop>
|
||||
<div class="full-width flex justify-center">
|
||||
|
@ -234,8 +275,8 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-pvp="{ value }">
|
||||
<QTd @click.stop
|
||||
><span> {{ value }}</span>
|
||||
<QTd @click.stop class="text-center">
|
||||
<span> {{ value }}</span>
|
||||
<QTooltip>
|
||||
{{ t('lastEntries.grouping') }}/{{ t('lastEntries.packing') }}
|
||||
</QTooltip></QTd
|
||||
|
@ -254,7 +295,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-cost="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QTd @click.stop class="text-center">
|
||||
<span>
|
||||
{{ toCurrency(row.cost, 'EUR', 3) }}
|
||||
<QTooltip>
|
||||
|
@ -272,10 +313,25 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-supplier="{ row }">
|
||||
<QTd @click.stop>
|
||||
<div class="full-width flex justify-center">
|
||||
<SupplierDescriptorProxy
|
||||
:id="row.supplierFk"
|
||||
class="q-ma-none"
|
||||
dense
|
||||
/>
|
||||
<span class="link">{{ row.supplier }}</span>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Hide inventory supplier: Ocultar proveedor inventario
|
||||
</i18n>
|
||||
<style lang="scss" scoped>
|
||||
.q-badge--rounded {
|
||||
border-radius: 50%;
|
||||
|
@ -287,4 +343,10 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
padding: 0 11px;
|
||||
height: 28px;
|
||||
}
|
||||
.th :first-child {
|
||||
.td {
|
||||
text-align: center;
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, computed, reactive } from 'vue';
|
||||
import { onMounted, ref, computed, reactive, watchEffect } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
|
||||
import { toDateFormat } from 'src/filters/date.js';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import axios from 'axios';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
|
||||
|
@ -21,8 +17,9 @@ const route = useRoute();
|
|||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
|
||||
const rowsSelected = ref([]);
|
||||
const tableRef = ref();
|
||||
const selectedRows = ref([]);
|
||||
const hasSelectedCards = computed(() => selectedRows.value.length > 0);
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
|
@ -36,6 +33,11 @@ const exprBuilder = (param, value) => {
|
|||
};
|
||||
|
||||
const params = reactive({ itemFk: route.params.id });
|
||||
const filter = reactive({
|
||||
where: {
|
||||
itemFk: route.params.id,
|
||||
},
|
||||
});
|
||||
|
||||
const arrayData = useArrayData('ItemShelvings', {
|
||||
url: 'ItemShelvingPlacementSupplyStocks',
|
||||
|
@ -44,123 +46,69 @@ const arrayData = useArrayData('ItemShelvings', {
|
|||
});
|
||||
const rows = computed(() => arrayData.store.data || []);
|
||||
|
||||
const applyColumnFilter = async (col) => {
|
||||
const paramKey = col.columnFilter?.filterParamKey || col.field;
|
||||
params[paramKey] = col.columnFilter.filterValue;
|
||||
await arrayData.addFilter({ filter: null, params });
|
||||
};
|
||||
|
||||
const getInputEvents = (col) => {
|
||||
return col.columnFilter.type === 'select'
|
||||
? { 'update:modelValue': () => applyColumnFilter(col) }
|
||||
: {
|
||||
'keyup.enter': () => applyColumnFilter(col),
|
||||
};
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
label: t('shelvings.created'),
|
||||
name: 'created',
|
||||
field: 'created',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: null,
|
||||
format: (val) => toDateFormat(val),
|
||||
columnFilter: false,
|
||||
format: (row) => toDateFormat(row.created),
|
||||
},
|
||||
|
||||
{
|
||||
label: t('shelvings.item'),
|
||||
name: 'item',
|
||||
field: 'itemFk',
|
||||
name: 'itemFk',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: null,
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
label: t('shelvings.concept'),
|
||||
name: 'concept',
|
||||
name: 'longName',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: null,
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
label: t('shelvings.parking'),
|
||||
name: 'parking',
|
||||
field: 'parking',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
format: (val) => dashIfEmpty(val),
|
||||
columnFilter: {
|
||||
component: VnSelect,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
url: 'parkings',
|
||||
fields: ['code'],
|
||||
'sort-by': 'code ASC',
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'parkings',
|
||||
fields: ['code'],
|
||||
'sort-by': 'code ASC',
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
columnField: { component: null },
|
||||
},
|
||||
{
|
||||
label: t('shelvings.shelving'),
|
||||
name: 'shelving',
|
||||
field: 'shelving',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
format: (val) => dashIfEmpty(val),
|
||||
columnFilter: {
|
||||
component: VnSelect,
|
||||
type: 'select',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
url: 'shelvings',
|
||||
fields: ['code'],
|
||||
'sort-by': 'code ASC',
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'shelvings',
|
||||
fields: ['code'],
|
||||
'sort-by': 'code ASC',
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
columnField: { component: null },
|
||||
},
|
||||
{
|
||||
label: t('shelvings.label'),
|
||||
name: 'label',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
format: (_, row) => (row.stock / row.packing).toFixed(2),
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterParamKey: 'label',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
columnFilter: { inWhere: true },
|
||||
format: (row) => (row.stock / row.packing).toFixed(2),
|
||||
},
|
||||
{
|
||||
label: t('shelvings.packing'),
|
||||
field: 'packing',
|
||||
name: 'packing',
|
||||
attrs: { inWhere: true },
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
columnFilter: {
|
||||
component: VnInput,
|
||||
type: 'text',
|
||||
filterValue: null,
|
||||
event: getInputEvents,
|
||||
attrs: {
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
format: (val) => dashIfEmpty(val),
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -169,15 +117,16 @@ const totalLabels = computed(() =>
|
|||
);
|
||||
|
||||
const removeLines = async () => {
|
||||
const itemShelvingIds = rowsSelected.value.map((row) => row.itemShelvingFk);
|
||||
const itemShelvingIds = selectedRows.value.map((row) => row.itemShelvingFk);
|
||||
await axios.post('ItemShelvings/deleteItemShelvings', { itemShelvingIds });
|
||||
rowsSelected.value = [];
|
||||
selectedRows.value = [];
|
||||
notify('shelvings.shelvingsRemoved', 'positive');
|
||||
await arrayData.fetch({ append: false });
|
||||
await tableRef.value.reload();
|
||||
};
|
||||
onMounted(async () => {
|
||||
await arrayData.fetch({ append: false });
|
||||
});
|
||||
watchEffect(selectedRows);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -203,7 +152,7 @@ onMounted(async () => {
|
|||
<QBtn
|
||||
color="primary"
|
||||
icon="delete"
|
||||
:disabled="!rowsSelected.length"
|
||||
:disabled="!hasSelectedCards"
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('shelvings.removeConfirmTitle'),
|
||||
|
@ -219,41 +168,27 @@ onMounted(async () => {
|
|||
</Teleport>
|
||||
</template>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<QTable
|
||||
:rows="rows"
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="ItemShelving"
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
class="full-width q-mt-md"
|
||||
selection="multiple"
|
||||
v-model:selected="rowsSelected"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
:url="`ItemShelvingPlacementSupplyStocks`"
|
||||
:filter="filter"
|
||||
:expr-builder="exprBuilder"
|
||||
:right-search="false"
|
||||
v-model:selected="selectedRows"
|
||||
:table="{
|
||||
'row-key': 'itemShelvingFk',
|
||||
selection: 'multiple',
|
||||
}"
|
||||
auto-load
|
||||
>
|
||||
<template #top-row="{ cols }">
|
||||
<QTr>
|
||||
<QTd />
|
||||
<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-concept="{ row }">
|
||||
<QTd @click.stop>
|
||||
<span class="link">{{ row.longName }}</span>
|
||||
<template #column-longName="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.longName }}
|
||||
<ItemDescriptorProxy :id="row.itemFk" />
|
||||
</QTd>
|
||||
</span>
|
||||
</template>
|
||||
</QTable>
|
||||
</VnTable>
|
||||
</QPage>
|
||||
</template>
|
||||
|
|
|
@ -46,7 +46,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
|||
<template #body="{ entity: { item, tags, visible, available, botanical } }">
|
||||
<QCard class="vn-one photo">
|
||||
<ItemDescriptorImage
|
||||
:entity-id="entityId"
|
||||
:entity-id="Number(entityId)"
|
||||
:visible="visible"
|
||||
:available="available"
|
||||
:show-edit-button="false"
|
||||
|
@ -89,7 +89,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
|
|||
<QCard class="vn-one">
|
||||
<VnTitle
|
||||
:url="getUrl(entityId, 'basic-data')"
|
||||
:text="t('item.summary.otherData')"
|
||||
:text="t('item.summary.basicData')"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('item.summary.intrastatCode')"
|
||||
|
|
|
@ -8,7 +8,6 @@ import VnRow from 'components/ui/VnRow.vue';
|
|||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
const route = useRoute();
|
||||
|
@ -60,6 +59,10 @@ const insertTag = (rows) => {
|
|||
itemTagsRef.value.formData[itemTagsRef.value.formData.length - 1].priority =
|
||||
getHighestPriority(rows);
|
||||
};
|
||||
|
||||
const submitTags = async (data) => {
|
||||
itemTagsRef.value.onSubmit(data);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -77,7 +80,6 @@ const insertTag = (rows) => {
|
|||
data-key="ItemTags"
|
||||
model="ItemTags"
|
||||
url="ItemTags"
|
||||
update-url="Tags/onSubmit"
|
||||
:data-required="{
|
||||
$index: undefined,
|
||||
itemFk: route.params.id,
|
||||
|
@ -138,6 +140,7 @@ const insertTag = (rows) => {
|
|||
:required="false"
|
||||
:rules="validate('itemTag.tagFk')"
|
||||
:use-like="false"
|
||||
sort-by="value"
|
||||
/>
|
||||
<VnInput
|
||||
v-else-if="
|
||||
|
@ -146,6 +149,7 @@ const insertTag = (rows) => {
|
|||
v-model="row.value"
|
||||
:label="t('itemTags.value')"
|
||||
:is-clearable="false"
|
||||
@keyup.enter.stop="submitTags(row)"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('itemBasicData.relevancy')"
|
||||
|
@ -153,6 +157,7 @@ const insertTag = (rows) => {
|
|||
v-model="row.priority"
|
||||
:required="true"
|
||||
:rules="validate('itemTag.priority')"
|
||||
@keyup.enter.stop="submitTags(row)"
|
||||
/>
|
||||
<div class="row justify-center" style="flex: 0">
|
||||
<QIcon
|
||||
|
@ -188,3 +193,8 @@ const insertTag = (rows) => {
|
|||
</QPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Tags can not be repeated: Las etiquetas no pueden repetirse
|
||||
</i18n>
|
||||
|
|
|
@ -28,7 +28,7 @@ const taxesFilter = {
|
|||
],
|
||||
};
|
||||
|
||||
const ItemTaxRef = ref(null);
|
||||
const ItemTaxRef = ref();
|
||||
const taxesOptions = ref([]);
|
||||
|
||||
const submitTaxes = async (data) => {
|
||||
|
@ -36,7 +36,10 @@ const submitTaxes = async (data) => {
|
|||
id: tax.id,
|
||||
taxClassFk: tax.taxClassFk,
|
||||
}));
|
||||
|
||||
if (payload.some((item) => item.taxClassFk === null)) {
|
||||
notify(t('Tax class cannot be blank'), 'negative');
|
||||
return;
|
||||
}
|
||||
await axios.post(`Items/updateTaxes`, payload);
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, reactive, onUnmounted, nextTick, computed } from 'vue';
|
||||
import { onMounted, ref, onUnmounted, nextTick, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||
|
@ -37,11 +37,9 @@ const fixedPrices = ref([]);
|
|||
const warehousesOptions = ref([]);
|
||||
const rowsSelected = ref([]);
|
||||
const itemFixedPriceFilterRef = ref();
|
||||
const params = reactive({});
|
||||
|
||||
onMounted(async () => {
|
||||
stateStore.rightDrawer = true;
|
||||
params.warehouseFk = user.value.warehouseFk;
|
||||
});
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
|
@ -137,8 +135,17 @@ const columns = computed(() => [
|
|||
...defaultColumnAttrs,
|
||||
columnClass: 'shrink',
|
||||
component: 'select',
|
||||
|
||||
options: warehousesOptions,
|
||||
columnFilter: {
|
||||
name: 'warehouseFk',
|
||||
inWhere: true,
|
||||
component: 'select',
|
||||
attrs: {
|
||||
options: warehousesOptions,
|
||||
'option-label': 'name',
|
||||
'option-value': 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
|
@ -210,8 +217,6 @@ const getRowUpdateInputEvents = (props, resetMinPrice, inputType = 'text') => {
|
|||
};
|
||||
|
||||
const updateMinPrice = async (value, props) => {
|
||||
// El checkbox hasMinPrice se encuentra en la misma columna que el input hasMinPrice
|
||||
// Por lo tanto le mandamos otro objeto con las mismas propiedades pero con el campo 'field' cambiado
|
||||
props.row.hasMinPrice = value;
|
||||
await upsertPrice({
|
||||
row: props.row,
|
||||
|
@ -220,12 +225,33 @@ const updateMinPrice = async (value, props) => {
|
|||
});
|
||||
};
|
||||
|
||||
const validations = ({ row }) => {
|
||||
const requiredFields = [
|
||||
'itemFk',
|
||||
'started',
|
||||
'ended',
|
||||
'rate2',
|
||||
'rate3',
|
||||
'warehouseFk',
|
||||
];
|
||||
const isValid = requiredFields.every(
|
||||
(field) => row[field] !== null && row[field] !== undefined
|
||||
);
|
||||
return isValid;
|
||||
};
|
||||
const upsertPrice = async (props, resetMinPrice = false) => {
|
||||
const { row } = props;
|
||||
if (tableRef.value.CrudModelRef.getChanges().updates.length > 0) {
|
||||
if (resetMinPrice) row.hasMinPrice = 0;
|
||||
await upsertFixedPrice(row);
|
||||
const isValid = validations({ ...props });
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
const { row } = props;
|
||||
const changes = tableRef.value.CrudModelRef.getChanges();
|
||||
if (changes?.updates?.length > 0) {
|
||||
if (resetMinPrice) row.hasMinPrice = 0;
|
||||
}
|
||||
if (!changes.updates && !changes.creates) return;
|
||||
const data = await upsertFixedPrice(row);
|
||||
tableRef.value.CrudModelRef.formData[props.rowIndex] = data;
|
||||
};
|
||||
|
||||
async function upsertFixedPrice(row) {
|
||||
|
@ -233,13 +259,6 @@ async function upsertFixedPrice(row) {
|
|||
return data;
|
||||
}
|
||||
|
||||
async function saveOnRowChange(row) {
|
||||
if (rowsSelected.value.length > 1) return;
|
||||
if (rowsSelected.value[0]?.id === row.id) return;
|
||||
else if (rowsSelected.value.length === 1) await upsertPrice(rowsSelected.value[0]);
|
||||
rowsSelected.value = [row];
|
||||
}
|
||||
|
||||
function checkLastVisibleRow() {
|
||||
let lastVisibleRow = null;
|
||||
|
||||
|
@ -255,39 +274,18 @@ function checkLastVisibleRow() {
|
|||
|
||||
const addRow = (original = null) => {
|
||||
let copy = null;
|
||||
if (!original) {
|
||||
const today = Date.vnNew();
|
||||
const millisecsInDay = 86400000;
|
||||
const daysInWeek = 7;
|
||||
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
|
||||
const today = Date.vnNew();
|
||||
const millisecsInDay = 86400000;
|
||||
const daysInWeek = 7;
|
||||
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
|
||||
|
||||
copy = {
|
||||
id: 0,
|
||||
started: today,
|
||||
ended: nextWeek,
|
||||
hasMinPrice: 0,
|
||||
$index: 0,
|
||||
};
|
||||
} else
|
||||
copy = {
|
||||
$index: original.$index - 1,
|
||||
itemFk: original.itemFk,
|
||||
name: original.name,
|
||||
subName: original.subName,
|
||||
value5: original.value5,
|
||||
value6: original.value6,
|
||||
value7: original.value7,
|
||||
value8: original.value8,
|
||||
value9: original.value9,
|
||||
value10: original.value10,
|
||||
warehouseFk: original.warehouseFk,
|
||||
rate2: original.rate2,
|
||||
rate3: original.rate3,
|
||||
hasMinPrice: original.hasMinPrice,
|
||||
minPrice: original.minPrice,
|
||||
started: Date.vnNew(),
|
||||
ended: Date.vnNew(),
|
||||
};
|
||||
copy = {
|
||||
id: 0,
|
||||
started: today,
|
||||
ended: nextWeek,
|
||||
hasMinPrice: 0,
|
||||
$index: 0,
|
||||
};
|
||||
return { original, copy };
|
||||
};
|
||||
|
||||
|
@ -300,7 +298,7 @@ function highlightNewRow({ $index: index }) {
|
|||
row.classList.add('highlight');
|
||||
setTimeout(() => {
|
||||
row.classList.remove('highlight');
|
||||
}, 3000); // Duración de la animación en milisegundos
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
const openEditTableCellDialog = () => {
|
||||
|
@ -411,9 +409,13 @@ function handleOnDataSave({ CrudModelRef }) {
|
|||
url="FixedPrices/filter"
|
||||
:order="['itemFk DESC', 'name DESC']"
|
||||
save-url="FixedPrices/crud"
|
||||
:user-params="{ warehouseFk: user.warehouseFk }"
|
||||
ref="tableRef"
|
||||
dense
|
||||
:filter="{
|
||||
where: {
|
||||
warehouseFk: user.warehouseFk,
|
||||
},
|
||||
}"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
|
@ -427,7 +429,6 @@ function handleOnDataSave({ CrudModelRef }) {
|
|||
disableInfiniteScroll: true,
|
||||
}"
|
||||
v-model:selected="rowsSelected"
|
||||
:row-click="saveOnRowChange"
|
||||
:create-as-dialog="false"
|
||||
:create="{
|
||||
onDataSaved: handleOnDataSave,
|
||||
|
|
|
@ -55,17 +55,6 @@ const columns = computed(() => [
|
|||
label: '',
|
||||
name: 'image',
|
||||
align: 'left',
|
||||
columnField: {
|
||||
component: VnImg,
|
||||
attrs: ({ row }) => {
|
||||
return {
|
||||
id: row?.id,
|
||||
zoomResolution: '1600x900',
|
||||
zoom: true,
|
||||
class: 'rounded',
|
||||
};
|
||||
},
|
||||
},
|
||||
columnFilter: false,
|
||||
cardVisible: true,
|
||||
},
|
||||
|
@ -184,7 +173,7 @@ const columns = computed(() => [
|
|||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
label: t('globals.origin'),
|
||||
label: t('item.list.origin'),
|
||||
name: 'origin',
|
||||
align: 'left',
|
||||
component: 'select',
|
||||
|
@ -228,7 +217,8 @@ const columns = computed(() => [
|
|||
},
|
||||
},
|
||||
{
|
||||
label: t('item.list.weightByPiece'),
|
||||
label: t('item.list.weight'),
|
||||
toolTip: t('item.list.weightByPiece'),
|
||||
name: 'weightByPiece',
|
||||
align: 'left',
|
||||
component: 'input',
|
||||
|
@ -322,7 +312,6 @@ const columns = computed(() => [
|
|||
ref="tableRef"
|
||||
data-key="ItemList"
|
||||
url="Items/filter"
|
||||
url-create="Items"
|
||||
:create="{
|
||||
urlCreate: 'Items',
|
||||
title: t('Create Item'),
|
||||
|
@ -333,12 +322,19 @@ const columns = computed(() => [
|
|||
}"
|
||||
:order="['isActive DESC', 'name', 'id']"
|
||||
:columns="columns"
|
||||
auto-load
|
||||
redirect="Item"
|
||||
:is-editable="false"
|
||||
:right-search="false"
|
||||
:filer="itemFilter"
|
||||
:filter="itemFilter"
|
||||
>
|
||||
<template #column-image="{ row }">
|
||||
<VnImg
|
||||
:id="row?.id"
|
||||
zoom-resolution="1600x900"
|
||||
:zoom="true"
|
||||
class="rounded"
|
||||
/>
|
||||
</template>
|
||||
<template #column-id="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.id }}
|
||||
|
@ -348,7 +344,7 @@ const columns = computed(() => [
|
|||
<template #column-userName="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.userName }}
|
||||
<WorkerDescriptorProxy :id="row.workerFk" />
|
||||
<WorkerDescriptorProxy :id="row.buyerFk" />
|
||||
</span>
|
||||
</template>
|
||||
<template #column-description="{ row }">
|
||||
|
|
|
@ -199,7 +199,17 @@ onMounted(async () => {
|
|||
dense
|
||||
outlined
|
||||
rounded
|
||||
/>
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{
|
||||
t(`params.${scope.opt?.name}`)
|
||||
}}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
|
@ -434,6 +444,13 @@ en:
|
|||
description: Description
|
||||
name: Name
|
||||
id: Id
|
||||
Accessories: Accessories
|
||||
Artificial: Artificial
|
||||
Flower: Flower
|
||||
Fruit: Fruit
|
||||
Green: Green
|
||||
Handmade: Handmade
|
||||
Plant: Plant
|
||||
es:
|
||||
More fields: Más campos
|
||||
params:
|
||||
|
@ -450,4 +467,11 @@ es:
|
|||
description: Descripción
|
||||
name: Nombre
|
||||
id: Id
|
||||
Accessories: Accesorios
|
||||
Artificial: Artificial
|
||||
Flower: Flor
|
||||
Fruit: Fruta
|
||||
Green: Verde
|
||||
Handmade: Hecho a mano
|
||||
Plant: Planta
|
||||
</i18n>
|
||||
|
|
|
@ -5,13 +5,16 @@ import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.v
|
|||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { dashIfEmpty, toCurrency } from 'filters/index';
|
||||
import { toCurrency } from 'filters/index';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
import ItemRequestDenyForm from './ItemRequestDenyForm.vue';
|
||||
import { toDate } from 'src/filters';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import ItemRequestFilter from './ItemRequestFilter.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
const stateStore = useStateStore();
|
||||
|
@ -228,6 +231,11 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<RightMenu>
|
||||
<template #right-panel>
|
||||
<ItemRequestFilter data-key="itemRequest" />
|
||||
</template>
|
||||
</RightMenu>
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="itemRequest"
|
||||
|
@ -239,6 +247,7 @@ onMounted(async () => {
|
|||
auto-load
|
||||
:disable-option="{ card: true }"
|
||||
chip-locale="item.params"
|
||||
:right-search="false"
|
||||
>
|
||||
<template #column-ticketFk="{ row }">
|
||||
<span class="link">
|
||||
|
@ -306,30 +315,28 @@ onMounted(async () => {
|
|||
/>
|
||||
</template>
|
||||
<template #column-denyOptions="{ row, rowIndex }">
|
||||
<QTd class="sticky no-padding">
|
||||
<QIcon
|
||||
v-if="row.response?.length"
|
||||
name="insert_drive_file"
|
||||
color="primary"
|
||||
size="sm"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ row.response }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row.isOk == null"
|
||||
name="thumb_down"
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="fill-icon"
|
||||
@click="showDenyRequestForm(row.id, rowIndex)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Discard') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</QTd>
|
||||
<QIcon
|
||||
v-if="row.response?.length"
|
||||
name="insert_drive_file"
|
||||
color="primary"
|
||||
size="sm"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ row.response }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
v-if="row.isOk == null"
|
||||
name="thumb_down"
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="fill-icon"
|
||||
@click="showDenyRequestForm(row.id, rowIndex)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Discard') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnTable>
|
||||
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { dateRange } from 'src/filters';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
|
@ -7,6 +7,8 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
|||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -21,7 +23,8 @@ const stateOptions = [
|
|||
{ code: 'accepted', name: t('accepted') },
|
||||
{ code: 'denied', name: t('denied') },
|
||||
];
|
||||
|
||||
const arrayData = useArrayData(props.dataKey);
|
||||
const fieldFiltersValues = ref([]);
|
||||
const itemTypesOptions = ref([]);
|
||||
const warehousesOptions = ref([]);
|
||||
|
||||
|
@ -56,6 +59,19 @@ const decrement = (paramsObj, key) => {
|
|||
|
||||
paramsObj[key]--;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
if (arrayData.store?.userParams) {
|
||||
fieldFiltersValues.value = Object.entries(arrayData.store.userParams).map(
|
||||
([key, value]) => ({
|
||||
name: key,
|
||||
value,
|
||||
selectedField: { name: key, label: t(`params.${key}`) },
|
||||
})
|
||||
);
|
||||
}
|
||||
exprBuilder('state', arrayData.store?.userParams?.state);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -145,33 +161,17 @@ const decrement = (paramsObj, key) => {
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
<VnSelectWorker
|
||||
:label="t('params.requesterFk')"
|
||||
v-model="params.requesterFk"
|
||||
@update:model-value="searchFn()"
|
||||
url="Workers/search"
|
||||
:fields="['id', 'name']"
|
||||
order="name ASC"
|
||||
:params="{ departmentCodes: ['VT'] }"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
|
||||
<QItemLabel caption
|
||||
>{{ scope.opt?.nickname }},
|
||||
{{ scope.opt?.code }}</QItemLabel
|
||||
>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
|
|
|
@ -10,7 +10,6 @@ const { t } = useI18n();
|
|||
url="ItemTypes"
|
||||
:label="t('Search item type')"
|
||||
:info="t('Search itemType by id, name or code')"
|
||||
search-url="table"
|
||||
/>
|
||||
</template>
|
||||
<i18n>
|
||||
|
|
|
@ -6,6 +6,7 @@ import VnTable from 'components/VnTable/VnTable.vue';
|
|||
import FetchData from 'components/FetchData.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import ItemTypeFilter from './ItemType/ItemTypeFilter.vue';
|
||||
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const tableRef = ref();
|
||||
|
@ -31,13 +32,14 @@ const columns = computed(() => [
|
|||
{
|
||||
align: 'left',
|
||||
name: 'name',
|
||||
label: t('name'),
|
||||
label: t('globals.name'),
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
label: t('worker'),
|
||||
name: 'workerFk',
|
||||
create: true,
|
||||
component: 'select',
|
||||
attrs: {
|
||||
|
@ -45,20 +47,20 @@ const columns = computed(() => [
|
|||
optionLabel: 'nickname',
|
||||
optionValue: 'id',
|
||||
},
|
||||
format: (row) => row.worker?.user?.name,
|
||||
cardVisible: true,
|
||||
visible: true,
|
||||
columnField: {
|
||||
component: 'userLink',
|
||||
attrs: ({ row }) => {
|
||||
return {
|
||||
workerId: row?.worker?.id,
|
||||
name: row.worker?.user?.name,
|
||||
defaultName: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
columnField: { component: null },
|
||||
columnFilter: {
|
||||
name: 'workerFk',
|
||||
attrs: {
|
||||
url: 'Workers/activeWithInheritedRole',
|
||||
fields: ['id', 'name'],
|
||||
where: { role: 'buyer' },
|
||||
optionFilter: 'firstName',
|
||||
optionLabel: 'name',
|
||||
optionValue: 'id',
|
||||
useLike: false,
|
||||
},
|
||||
inWhere: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -135,24 +137,27 @@ const columns = computed(() => [
|
|||
:columns="columns"
|
||||
auto-load
|
||||
:right-search="false"
|
||||
:is-editable="false"
|
||||
:use-model="true"
|
||||
redirect="item/item-type"
|
||||
/>
|
||||
>
|
||||
<template #column-workerFk="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.worker?.user?.name }}
|
||||
<WorkerDescriptorProxy :id="row.workerFk" />
|
||||
</span>
|
||||
</template>
|
||||
</VnTable>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
id: Id
|
||||
code: Código
|
||||
name: Nombre
|
||||
worker: Trabajador
|
||||
ItemCategory: Reino
|
||||
Temperature: Temperatura
|
||||
Create ItemTypes: Crear familia
|
||||
en:
|
||||
code: Code
|
||||
name: Name
|
||||
worker: Worker
|
||||
ItemCategory: ItemCategory
|
||||
Temperature: Temperature
|
||||
|
|
|
@ -66,6 +66,7 @@ lastEntries:
|
|||
package: Package
|
||||
freight: Freight
|
||||
comission: Comission
|
||||
printedStickers: Pri.
|
||||
itemTags:
|
||||
removeTag: Remove tag
|
||||
addTag: Add tag
|
||||
|
@ -95,6 +96,15 @@ item:
|
|||
mine: For me
|
||||
state: State
|
||||
myTeam: My team
|
||||
shipped: Shipped
|
||||
description: Description
|
||||
quantity: Quantity
|
||||
price: Price
|
||||
item: Item
|
||||
achieved: Achieved
|
||||
concept: Concept
|
||||
denyOptions: Deny
|
||||
scopeDays: Scope days
|
||||
searchbar:
|
||||
label: Search item
|
||||
descriptor:
|
||||
|
@ -112,7 +122,7 @@ item:
|
|||
title: All its properties will be copied
|
||||
subTitle: Do you want to clone this item?
|
||||
list:
|
||||
id: Identifier
|
||||
id: Id
|
||||
grouping: Grouping
|
||||
packing: Packing
|
||||
description: Description
|
||||
|
@ -122,8 +132,9 @@ item:
|
|||
intrastat: Intrastat
|
||||
isActive: Active
|
||||
size: Size
|
||||
origin: Origin
|
||||
origin: Orig.
|
||||
userName: Buyer
|
||||
weight: Weight
|
||||
weightByPiece: Weight/Piece
|
||||
stemMultiplier: Multiplier
|
||||
producer: Producer
|
||||
|
|
|
@ -56,7 +56,7 @@ lastEntries:
|
|||
landed: F. Entrega
|
||||
entry: Entrada
|
||||
pvp: PVP
|
||||
label: Etiquetas
|
||||
label: Eti.
|
||||
grouping: Grouping
|
||||
quantity: Cantidad
|
||||
cost: Coste
|
||||
|
@ -66,6 +66,7 @@ lastEntries:
|
|||
package: Embalaje
|
||||
freight: Porte
|
||||
comission: Comisión
|
||||
printedStickers: Imp.
|
||||
itemTags:
|
||||
removeTag: Quitar etiqueta
|
||||
addTag: Añadir etiqueta
|
||||
|
@ -97,6 +98,15 @@ item:
|
|||
mine: Para mi
|
||||
state: Estado
|
||||
myTeam: Mi equipo
|
||||
shipped: Enviado
|
||||
description: Descripción
|
||||
quantity: Cantidad
|
||||
price: Precio
|
||||
item: Artículo
|
||||
achieved: Conseguido
|
||||
concept: Concepto
|
||||
denyOptions: Denegado
|
||||
scopeDays: Días en adelante
|
||||
searchbar:
|
||||
label: Buscar artículo
|
||||
descriptor:
|
||||
|
@ -114,7 +124,7 @@ item:
|
|||
title: Todas sus propiedades serán copiadas
|
||||
subTitle: ¿Desea clonar este artículo?
|
||||
list:
|
||||
id: Identificador
|
||||
id: Id
|
||||
grouping: Grouping
|
||||
packing: Packing
|
||||
description: Descripción
|
||||
|
@ -124,7 +134,8 @@ item:
|
|||
intrastat: Intrastat
|
||||
isActive: Activo
|
||||
size: Medida
|
||||
origin: Origen
|
||||
origin: Orig.
|
||||
weight: Peso
|
||||
weightByPiece: Peso (gramos)/tallo
|
||||
userName: Comprador
|
||||
stemMultiplier: Multiplicador
|
||||
|
|
|
@ -9,6 +9,7 @@ import VnInput from 'src/components/common/VnInput.vue';
|
|||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import { dateRange } from 'src/filters';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
defineProps({ dataKey: { type: String, required: true } });
|
||||
const { t, te } = useI18n();
|
||||
|
@ -59,7 +60,11 @@ const getLocale = (label) => {
|
|||
</template>
|
||||
<template #customTags="{ params, searchFn, formatFn }">
|
||||
<VnFilterPanelChip
|
||||
v-if="params.scopeDays !== null"
|
||||
v-if="
|
||||
params.scopeDays !== undefined ||
|
||||
params.scopeDays !== '' ||
|
||||
params.scopeDays !== null
|
||||
"
|
||||
removable
|
||||
@remove="handleScopeDays(params, null, searchFn)"
|
||||
>
|
||||
|
@ -108,33 +113,16 @@ const getLocale = (label) => {
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
<VnSelectWorker
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
:label="t('globals.params.salesPersonFk')"
|
||||
v-model="params.salesPersonFk"
|
||||
url="Workers/search"
|
||||
:params="{ departmentCodes: ['VT'] }"
|
||||
is-outlined
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
:no-one="true"
|
||||
>
|
||||
<template #option="{ opt, itemProps }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel
|
||||
v-if="opt.code"
|
||||
class="text-grey text-caption"
|
||||
>
|
||||
{{ `${opt.nickname}, ${opt.code}` }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
</VnSelectWorker>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
|
@ -146,6 +134,7 @@ const getLocale = (label) => {
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
|
@ -197,6 +186,18 @@ const getLocale = (label) => {
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
:label="t('globals.params.countryFk')"
|
||||
v-model="params.countryFk"
|
||||
url="Countries"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
|
@ -209,6 +210,34 @@ const getLocale = (label) => {
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
:label="t('globals.params.departmentFk')"
|
||||
v-model="params.department"
|
||||
option-label="name"
|
||||
option-value="name"
|
||||
url="Departments"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
:label="t('globals.params.packing')"
|
||||
v-model="params.packing"
|
||||
url="ItemPackingTypes"
|
||||
option-label="code"
|
||||
option-value="code"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
|
@ -258,7 +287,7 @@ en:
|
|||
ON_PREVIOUS: On previous
|
||||
PACKED: Packed
|
||||
No one: No one
|
||||
|
||||
|
||||
es:
|
||||
params:
|
||||
orderFk: Id cesta
|
||||
|
|
|
@ -10,20 +10,23 @@ import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
|||
import TicketSummary from 'src/pages/Ticket/Card/TicketSummary.vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { toDateFormat } from 'src/filters/date.js';
|
||||
import { toCurrency, dateRange, dashIfEmpty } from 'src/filters';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import MonitorTicketSearchbar from './MonitorTicketSearchbar.vue';
|
||||
import MonitorTicketFilter from './MonitorTicketFilter.vue';
|
||||
import TicketProblems from 'src/components/TicketProblems.vue';
|
||||
import VnDateBadge from 'src/components/common/VnDateBadge.vue';
|
||||
|
||||
const DEFAULT_AUTO_REFRESH = 2 * 60 * 1000; // 2min in ms
|
||||
const DEFAULT_AUTO_REFRESH = 2 * 60 * 1000;
|
||||
const { t } = useI18n();
|
||||
const autoRefresh = ref(false);
|
||||
const tableRef = ref(null);
|
||||
const provinceOpts = ref([]);
|
||||
const stateOpts = ref([]);
|
||||
const zoneOpts = ref([]);
|
||||
const DepartmentOpts = ref([]);
|
||||
const ItemPackingTypeOpts = ref([]);
|
||||
const visibleColumns = ref([]);
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
||||
const [from, to] = dateRange(Date.vnNew());
|
||||
|
@ -51,6 +54,8 @@ function exprBuilder(param, value) {
|
|||
case 'nickname':
|
||||
return { [`t.nickname`]: { like: `%${value}%` } };
|
||||
case 'zoneFk':
|
||||
case 'department':
|
||||
return { 'd.name': value };
|
||||
case 'totalWithVat':
|
||||
return { [`t.${param}`]: value };
|
||||
}
|
||||
|
@ -137,6 +142,7 @@ const columns = computed(() => [
|
|||
align: 'left',
|
||||
format: (row) => row.practicalHour,
|
||||
columnFilter: false,
|
||||
dense: true,
|
||||
},
|
||||
{
|
||||
label: t('salesTicketsTable.preparation'),
|
||||
|
@ -190,6 +196,7 @@ const columns = computed(() => [
|
|||
'false-value': 0,
|
||||
'true-value': 1,
|
||||
},
|
||||
component: false,
|
||||
},
|
||||
{
|
||||
label: t('salesTicketsTable.zone'),
|
||||
|
@ -206,6 +213,12 @@ const columns = computed(() => [
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('salesTicketsTable.payMethod'),
|
||||
name: 'payMethod',
|
||||
align: 'left',
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
label: t('salesTicketsTable.total'),
|
||||
name: 'totalWithVat',
|
||||
|
@ -219,6 +232,36 @@ const columns = computed(() => [
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('salesTicketsTable.department'),
|
||||
name: 'department',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: 'select',
|
||||
url: 'Departments',
|
||||
attrs: {
|
||||
options: DepartmentOpts.value,
|
||||
optionValue: 'name',
|
||||
optionLabel: 'name',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('salesTicketsTable.packing'),
|
||||
name: 'packing',
|
||||
align: 'left',
|
||||
columnFilter: {
|
||||
component: 'select',
|
||||
url: 'ItemPackingTypes',
|
||||
attrs: {
|
||||
options: ItemPackingTypeOpts.value,
|
||||
'option-value': 'code',
|
||||
'option-label': 'code',
|
||||
dense: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
name: 'tableActions',
|
||||
|
@ -250,19 +293,6 @@ const columns = computed(() => [
|
|||
},
|
||||
]);
|
||||
|
||||
const getBadgeAttrs = (date) => {
|
||||
let today = Date.vnNew();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
let timeTicket = new Date(date);
|
||||
timeTicket.setHours(0, 0, 0, 0);
|
||||
|
||||
let timeDiff = today - timeTicket;
|
||||
|
||||
if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' };
|
||||
if (timeDiff < 0) return { color: 'success', 'text-color': 'black' };
|
||||
return { color: 'transparent', 'text-color': 'white' };
|
||||
};
|
||||
|
||||
let refreshTimer = null;
|
||||
|
||||
const autoRefreshHandler = (value) => {
|
||||
|
@ -279,14 +309,6 @@ const totalPriceColor = (ticket) => {
|
|||
if (total > 0 && total < 50) return 'warning';
|
||||
};
|
||||
|
||||
const formatShippedDate = (date) => {
|
||||
if (!date) return '-';
|
||||
const dateSplit = date.split('T');
|
||||
const [year, month, day] = dateSplit[0].split('-');
|
||||
const newDate = new Date(year, month - 1, day);
|
||||
return toDateFormat(newDate);
|
||||
};
|
||||
|
||||
const openTab = (id) =>
|
||||
window.open(`#/ticket/${id}/sale`, '_blank', 'noopener, noreferrer');
|
||||
</script>
|
||||
|
@ -318,6 +340,24 @@ const openTab = (id) =>
|
|||
auto-load
|
||||
@on-fetch="(data) => (zoneOpts = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="ItemPackingTypes"
|
||||
:filter="{
|
||||
fields: ['code'],
|
||||
order: 'code ASC',
|
||||
}"
|
||||
auto-load
|
||||
@on-fetch="(data) => (ItemPackingTypeOpts = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Departments"
|
||||
:filter="{
|
||||
fields: ['id', 'name'],
|
||||
order: 'id ASC',
|
||||
}"
|
||||
auto-load
|
||||
@on-fetch="(data) => (DepartmentOpts = data)"
|
||||
/>
|
||||
<MonitorTicketSearchbar />
|
||||
<RightMenu>
|
||||
<template #right-panel>
|
||||
|
@ -337,7 +377,7 @@ const openTab = (id) =>
|
|||
auto-load
|
||||
:row-click="({ id }) => openTab(id)"
|
||||
:disable-option="{ card: true }"
|
||||
:user-params="{ from, to, scopeDays: 0 }"
|
||||
:user-params="{ from, to, scopeDays: 0, packing }"
|
||||
>
|
||||
<template #top-left>
|
||||
<QBtn
|
||||
|
@ -382,13 +422,7 @@ const openTab = (id) =>
|
|||
</div>
|
||||
</template>
|
||||
<template #column-shippedDate="{ row }">
|
||||
<QBadge
|
||||
v-bind="getBadgeAttrs(row.shippedDate)"
|
||||
class="q-pa-sm"
|
||||
style="font-size: 14px"
|
||||
>
|
||||
{{ formatShippedDate(row.shippedDate) }}
|
||||
</QBadge>
|
||||
<VnDateBadge :date="row.shippedDate" />
|
||||
</template>
|
||||
<template #column-provinceFk="{ row }">
|
||||
<span :title="row.province" v-text="row.province" />
|
||||
|
|
|
@ -26,8 +26,8 @@ salesTicketsTable:
|
|||
componentLack: Component lack
|
||||
tooLittle: Ticket too little
|
||||
identifier: Identifier
|
||||
theoretical: Theoretical
|
||||
practical: Practical
|
||||
theoretical: H.Theor
|
||||
practical: H.Prac
|
||||
province: Province
|
||||
state: State
|
||||
isFragile: Is fragile
|
||||
|
@ -35,7 +35,10 @@ salesTicketsTable:
|
|||
goToLines: Go to lines
|
||||
preview: Preview
|
||||
total: Total
|
||||
preparation: Preparation
|
||||
preparation: H.Prep
|
||||
payMethod: Pay method
|
||||
department: Department
|
||||
packing: ITP
|
||||
searchBar:
|
||||
label: Search tickets
|
||||
info: Search tickets by id or alias
|
||||
|
|
|
@ -26,8 +26,8 @@ salesTicketsTable:
|
|||
componentLack: Faltan componentes
|
||||
tooLittle: Ticket demasiado pequeño
|
||||
identifier: Identificador
|
||||
theoretical: Teórica
|
||||
practical: Práctica
|
||||
theoretical: H.Teór
|
||||
practical: H.Prác
|
||||
province: Provincia
|
||||
state: Estado
|
||||
isFragile: Es frágil
|
||||
|
@ -35,7 +35,10 @@ salesTicketsTable:
|
|||
goToLines: Ir a líneas
|
||||
preview: Vista previa
|
||||
total: Total
|
||||
preparation: Preparación
|
||||
preparation: H.Prep
|
||||
payMethod: Método de pago
|
||||
department: Departamento
|
||||
packing: ITP
|
||||
searchBar:
|
||||
label: Buscar tickets
|
||||
info: Buscar tickets por identificador o alias
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { onMounted, onUnmounted, ref, computed, watch } from 'vue';
|
||||
import { onMounted, ref, computed, watch, provide } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
|
@ -18,6 +18,7 @@ const dataKey = 'OrderCatalogList';
|
|||
const arrayData = useArrayData(dataKey);
|
||||
const store = arrayData.store;
|
||||
const tags = ref([]);
|
||||
const itemRefs = ref({});
|
||||
|
||||
let catalogParams = {
|
||||
orderFk: route.params.id,
|
||||
|
@ -29,8 +30,6 @@ onMounted(() => {
|
|||
checkOrderConfirmation();
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
async function checkOrderConfirmation() {
|
||||
const response = await axios.get(`Orders/${route.params.id}`);
|
||||
if (response.data.isConfirmed === 1) {
|
||||
|
@ -115,6 +114,7 @@ watch(
|
|||
<CatalogItem
|
||||
v-for="row in rows"
|
||||
:key="row.id"
|
||||
:ref="(el) => (itemRefs[row.id] = el)"
|
||||
:item="row"
|
||||
is-catalog
|
||||
class="fill-icon"
|
||||
|
|
|
@ -65,7 +65,6 @@ const selectCategory = async (params, category, search) => {
|
|||
params.typeFk = null;
|
||||
params.categoryFk = category.id;
|
||||
await loadTypes(category?.id);
|
||||
await search();
|
||||
};
|
||||
|
||||
const loadTypes = async (id) => {
|
||||
|
|
|
@ -1,41 +1,69 @@
|
|||
<script setup>
|
||||
import toCurrency from '../../../filters/toCurrency';
|
||||
import { ref } from 'vue';
|
||||
import toCurrency from 'src/filters/toCurrency';
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useNotify from 'composables/useNotify';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
const emit = defineEmits(['added']);
|
||||
const route = useRoute();
|
||||
const props = defineProps({
|
||||
prices: {
|
||||
item: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const state = useState();
|
||||
|
||||
const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 })));
|
||||
const descriptorData = useArrayData('orderData');
|
||||
const orderData = computed(() => state.get('orderData'));
|
||||
|
||||
const prices = ref((props.item.prices || []).map((item) => ({ ...item, quantity: 0 })));
|
||||
const isLoading = ref(false);
|
||||
|
||||
const totalQuantity = (items) =>
|
||||
items.reduce((acc, item) => {
|
||||
return acc + item.quantity;
|
||||
}, 0);
|
||||
const addToOrder = async () => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
const items = (fields.value || []).filter((item) => Number(item.quantity) > 0);
|
||||
const items = (prices.value || []).filter((item) => Number(item.quantity) > 0);
|
||||
await axios.post('/OrderRows/addToOrder', {
|
||||
items,
|
||||
orderFk: Number(route.params.id),
|
||||
});
|
||||
|
||||
const { data: orderTotal } = await axios.get(
|
||||
`Orders/${Number(route.params.id)}/getTotal`
|
||||
);
|
||||
|
||||
state.set('orderTotal', orderTotal);
|
||||
const rows = orderData.value.rows.push(...items) || [];
|
||||
state.set('orderData', {
|
||||
...orderData.value,
|
||||
rows,
|
||||
});
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
emit('added');
|
||||
descriptorData.fetch({});
|
||||
emit('added', -totalQuantity(items));
|
||||
isLoading.value = false;
|
||||
};
|
||||
const canAddToOrder = () => {
|
||||
return (fields.value || []).some((item) => Number(item.quantity) > 0);
|
||||
let canAddToOrder = (prices.value || []).some((price) => Number(price.quantity) > 0);
|
||||
if (canAddToOrder) {
|
||||
const excedQuantity = prices.value.reduce(
|
||||
(acc, { quantity }) => acc + quantity,
|
||||
0
|
||||
);
|
||||
if (excedQuantity > props.item.available) {
|
||||
canAddToOrder = false;
|
||||
}
|
||||
}
|
||||
return canAddToOrder;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -44,30 +72,33 @@ const canAddToOrder = () => {
|
|||
<QForm @submit="addToOrder">
|
||||
<QMarkupTable class="shadow-0">
|
||||
<tbody>
|
||||
<tr v-for="item in fields" :key="item.warehouse">
|
||||
<tr v-for="price in prices" :key="price.warehouse">
|
||||
<td class="text-bold q-pr-md td" style="width: 35%">
|
||||
{{ item.warehouse }}
|
||||
{{ price.warehouse }}
|
||||
</td>
|
||||
<td class="text-right" style="width: 35%">
|
||||
<span
|
||||
class="link"
|
||||
@click="
|
||||
@click.shift="
|
||||
() => {
|
||||
item.quantity += item.grouping;
|
||||
price.quantity -= price.grouping;
|
||||
}
|
||||
"
|
||||
@click.exact="
|
||||
() => {
|
||||
price.quantity += price.grouping;
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ item.grouping }}
|
||||
{{ price.grouping }}
|
||||
</span>
|
||||
x {{ toCurrency(item.price) }}
|
||||
x {{ toCurrency(price.price) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<QInput
|
||||
v-model.number="item.quantity"
|
||||
type="number"
|
||||
:step="item.grouping"
|
||||
<VnInputNumber
|
||||
v-model.number="price.quantity"
|
||||
:step="price.grouping"
|
||||
min="0"
|
||||
dense
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
import { useState } from 'composables/useState';
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
|
@ -11,29 +10,12 @@ import VnInputDate from 'components/common/VnInputDate.vue';
|
|||
import { useDialogPluginComponent } from 'quasar';
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = useState();
|
||||
const ORDER_MODEL = 'order';
|
||||
|
||||
const router = useRouter();
|
||||
const agencyList = ref([]);
|
||||
const addressList = ref([]);
|
||||
defineEmits(['confirm', ...useDialogPluginComponent.emits]);
|
||||
|
||||
const fetchAddressList = async (addressId) => {
|
||||
const { data } = await axios.get('addresses', {
|
||||
params: {
|
||||
filter: JSON.stringify({
|
||||
fields: ['id', 'nickname', 'street', 'city'],
|
||||
where: { id: addressId },
|
||||
}),
|
||||
},
|
||||
});
|
||||
addressList.value = data;
|
||||
if (addressList.value?.length === 1) {
|
||||
state.get(ORDER_MODEL).addressId = addressList.value[0].id;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAgencyList = async (landed, addressFk) => {
|
||||
if (!landed || !addressFk) {
|
||||
return;
|
||||
|
@ -59,17 +41,9 @@ const initialFormState = reactive({
|
|||
clientFk: $props.clientFk,
|
||||
});
|
||||
|
||||
const onClientChange = async (clientId = $props.clientFk) => {
|
||||
const { data } = await axios.get(`Clients/${clientId}`);
|
||||
await fetchAddressList(data.defaultAddressFk);
|
||||
};
|
||||
|
||||
async function onDataSaved(_, id) {
|
||||
await router.push({ path: `/order/${id}/catalog` });
|
||||
}
|
||||
onMounted(async () => {
|
||||
await onClientChange();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -90,10 +64,9 @@ onMounted(async () => {
|
|||
option-value="id"
|
||||
option-label="name"
|
||||
:filter="{
|
||||
fields: ['id', 'name', 'defaultAddressFk'],
|
||||
fields: ['id', 'name'],
|
||||
}"
|
||||
hide-selected
|
||||
@update:model-value="onClientChange"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -110,7 +83,7 @@ onMounted(async () => {
|
|||
:label="t('order.form.addressFk')"
|
||||
v-model="data.addressId"
|
||||
url="addresses"
|
||||
:fields="['id', 'nickname', 'defaultAddressFk', 'street', 'city']"
|
||||
:fields="['id', 'nickname', 'street', 'city']"
|
||||
sort-by="id"
|
||||
option-value="id"
|
||||
option-label="street"
|
||||
|
|
|
@ -63,21 +63,26 @@ const setData = (entity) => {
|
|||
if (!entity) return;
|
||||
getTotalRef.value && getTotalRef.value.fetch();
|
||||
data.value = useCardDescription(entity?.client?.name, entity?.id);
|
||||
state.set('orderData', entity);
|
||||
state.set('orderTotal', total);
|
||||
};
|
||||
|
||||
const getConfirmationValue = (isConfirmed) => {
|
||||
return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed');
|
||||
};
|
||||
|
||||
const total = ref(null);
|
||||
const orderTotal = computed(() => state.get('orderTotal') ?? 0);
|
||||
const total = ref(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="getTotalRef"
|
||||
:url="`Orders/${entityId}/getTotal`"
|
||||
@on-fetch="(response) => (total = response)"
|
||||
@on-fetch="
|
||||
(response) => {
|
||||
total = response;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<CardDescriptor
|
||||
ref="descriptor"
|
||||
|
@ -112,7 +117,7 @@ const total = ref(null);
|
|||
:label="t('order.summary.items')"
|
||||
:value="(entity?.rows?.length || DEFAULT_ITEMS).toString()"
|
||||
/>
|
||||
<VnLv :label="t('order.summary.total')" :value="toCurrency(total)" />
|
||||
<VnLv :label="t('order.summary.total')" :value="toCurrency(orderTotal)" />
|
||||
</template>
|
||||
<template #actions="{ entity }">
|
||||
<QCardActions>
|
||||
|
|
|
@ -6,6 +6,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -61,28 +62,16 @@ const sourceList = ref([]);
|
|||
outlined
|
||||
rounded
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('salesPerson')"
|
||||
<VnSelectWorker
|
||||
:label="t('globals.salesPerson')"
|
||||
v-model="params.workerFk"
|
||||
url="Workers/search"
|
||||
:filter="{ departmentCodes: ['VT'] }"
|
||||
sort-by="nickname ASC"
|
||||
option-label="nickname"
|
||||
:params="{
|
||||
departmentCodes: ['VT'],
|
||||
}"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }},{{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
/>
|
||||
<VnInputDate
|
||||
v-model="params.from"
|
||||
:label="t('fromLanded')"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
|
||||
import OrderSummary from 'pages/Order/Card/OrderSummary.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
|
@ -15,14 +15,13 @@ import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vu
|
|||
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import { toDateTimeFormat } from 'src/filters/date';
|
||||
import { useRoute } from 'vue-router';
|
||||
import dataByOrder from 'src/utils/dataByOrder';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const tableRef = ref();
|
||||
const agencyList = ref([]);
|
||||
const addressesList = ref([]);
|
||||
const route = useRoute();
|
||||
const addressOptions = ref([]);
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -148,16 +147,12 @@ onMounted(() => {
|
|||
const id = JSON.parse(clientId);
|
||||
fetchClientAddress(id.clientFk);
|
||||
});
|
||||
|
||||
async function fetchClientAddress(id, formData = {}) {
|
||||
const { data } = await axios.get(`Clients/${id}`, {
|
||||
params: {
|
||||
filter: {
|
||||
order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'],
|
||||
include: { relation: 'addresses' },
|
||||
},
|
||||
},
|
||||
});
|
||||
addressesList.value = data.addresses;
|
||||
const { data } = await axios.get(
|
||||
`Clients/${id}/addresses?filter[order]=isActive DESC`
|
||||
);
|
||||
addressOptions.value = data;
|
||||
formData.addressId = data.defaultAddressFk;
|
||||
fetchAgencies(formData);
|
||||
}
|
||||
|
@ -168,7 +163,7 @@ async function fetchAgencies({ landed, addressId }) {
|
|||
const { data } = await axios.get('Agencies/landsThatDay', {
|
||||
params: { addressFk: addressId, landed },
|
||||
});
|
||||
agencyList.value = dataByOrder(data, 'agencyMode ASC');
|
||||
agencyList.value = data;
|
||||
}
|
||||
|
||||
const getDateColor = (date) => {
|
||||
|
@ -252,34 +247,29 @@ const getDateColor = (date) => {
|
|||
</VnSelect>
|
||||
<VnSelect
|
||||
v-model="data.addressId"
|
||||
:options="addressesList"
|
||||
:options="addressOptions"
|
||||
:label="t('module.address')"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
@update:model-value="() => fetchAgencies(data)"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem
|
||||
v-bind="scope.itemProps"
|
||||
:class="{ disabled: !scope.opt.isActive }"
|
||||
>
|
||||
<QItemSection style="min-width: min-content" avatar>
|
||||
<QIcon
|
||||
v-if="
|
||||
scope.opt.isActive && data.addressId === scope.opt.id
|
||||
"
|
||||
size="sm"
|
||||
color="grey"
|
||||
name="star"
|
||||
class="fill-icon"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt.nickname }}
|
||||
</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ `${scope.opt.street}, ${scope.opt.city}` }}
|
||||
<QItemLabel
|
||||
:class="{
|
||||
'color-vn-label': !scope.opt?.isActive,
|
||||
}"
|
||||
>
|
||||
{{
|
||||
`${
|
||||
!scope.opt?.isActive
|
||||
? t('basicData.inactive')
|
||||
: ''
|
||||
} `
|
||||
}}
|
||||
{{ scope.opt?.nickname }}: {{ scope.opt?.street }},
|
||||
{{ scope.opt?.city }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
|
|
@ -4,6 +4,7 @@ import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
|
|||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -31,29 +32,13 @@ const emit = defineEmits(['search']);
|
|||
<template #body="{ params }">
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('Worker')"
|
||||
<VnSelectWorker
|
||||
v-model="params.workerFk"
|
||||
url="Workers/search"
|
||||
sort-by="nickname ASC"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:input-debounce="0"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }},{{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
|
|
|
@ -11,6 +11,7 @@ import VnInputDate from 'components/common/VnInputDate.vue';
|
|||
import VnInput from 'components/common/VnInput.vue';
|
||||
import axios from 'axios';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
@ -94,26 +95,7 @@ const onSave = (data, response) => {
|
|||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
:label="t('Worker')"
|
||||
v-model="data.workerFk"
|
||||
url="Workers/search"
|
||||
sort-by="nickname ASC"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
:input-debounce="0"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }}, {{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<VnSelectWorker v-model="data.workerFk" />
|
||||
<VnSelect
|
||||
:label="t('Vehicle')"
|
||||
v-model="data.vehicleFk"
|
||||
|
|
|
@ -223,10 +223,10 @@ function navigate(id) {
|
|||
router.push({ path: `/route/${id}` });
|
||||
}
|
||||
|
||||
const cloneRoutes = () => {
|
||||
const cloneRoutes = async () => {
|
||||
if (!selectedRows.value.length || !startingDate.value) return;
|
||||
axios.post('Routes/clone', {
|
||||
created: startingDate.value,
|
||||
await axios.post('Routes/clone', {
|
||||
dated: startingDate.value,
|
||||
ids: selectedRows.value.map((row) => row?.id),
|
||||
});
|
||||
startingDate.value = null;
|
||||
|
@ -274,7 +274,6 @@ const openTicketsDialog = (id) => {
|
|||
<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')"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import CardList from 'components/ui/CardList.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
@ -21,7 +21,6 @@ const filter = {
|
|||
};
|
||||
|
||||
onMounted(() => (stateStore.rightDrawer = true));
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
function navigate(id) {
|
||||
router.push({ path: `/shelving/${id}` });
|
||||
|
|
|
@ -24,13 +24,14 @@ const supplier = ref(null);
|
|||
const supplierAccountRef = ref(null);
|
||||
const wireTransferFk = ref(null);
|
||||
const bankEntitiesOptions = ref([]);
|
||||
const filteredBankEntitiesOptions = ref([]);
|
||||
|
||||
const onBankEntityCreated = async (dataSaved, rowData) => {
|
||||
await bankEntitiesRef.value.fetch();
|
||||
rowData.bankEntityFk = dataSaved.id;
|
||||
};
|
||||
|
||||
const onChangesSaved = () => {
|
||||
const onChangesSaved = async () => {
|
||||
if (supplier.value.payMethodFk !== wireTransferFk.value)
|
||||
quasar
|
||||
.dialog({
|
||||
|
@ -55,12 +56,35 @@ const setWireTransfer = async () => {
|
|||
await axios.patch(`Suppliers/${route.params.id}`, params);
|
||||
notify('globals.dataSaved', 'positive');
|
||||
};
|
||||
|
||||
function findBankFk(value, row) {
|
||||
row.bankEntityFk = null;
|
||||
if (!value) return;
|
||||
|
||||
const bankEntityFk = bankEntitiesOptions.value.find((b) => b.id == value.slice(4, 8));
|
||||
if (bankEntityFk) row.bankEntityFk = bankEntityFk.id;
|
||||
}
|
||||
|
||||
function bankEntityFilter(val, update) {
|
||||
update(() => {
|
||||
const needle = val.toLowerCase();
|
||||
filteredBankEntitiesOptions.value = bankEntitiesOptions.value.filter(
|
||||
(bank) =>
|
||||
bank.bic.toLowerCase().startsWith(needle) ||
|
||||
bank.name.toLowerCase().includes(needle)
|
||||
);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
ref="bankEntitiesRef"
|
||||
url="BankEntities"
|
||||
@on-fetch="(data) => (bankEntitiesOptions = data)"
|
||||
@on-fetch="
|
||||
(data) => {
|
||||
(bankEntitiesOptions = data), (filteredBankEntitiesOptions = data);
|
||||
}
|
||||
"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
|
@ -98,6 +122,7 @@ const setWireTransfer = async () => {
|
|||
<VnInput
|
||||
:label="t('supplier.accounts.iban')"
|
||||
v-model="row.iban"
|
||||
@update:model-value="(value) => findBankFk(value, row)"
|
||||
:required="true"
|
||||
>
|
||||
<template #append>
|
||||
|
@ -109,7 +134,9 @@ const setWireTransfer = async () => {
|
|||
<VnSelectDialog
|
||||
:label="t('worker.create.bankEntity')"
|
||||
v-model="row.bankEntityFk"
|
||||
:options="bankEntitiesOptions"
|
||||
:options="filteredBankEntitiesOptions"
|
||||
:default-filter="false"
|
||||
@filter="(val, update) => bankEntityFilter(val, update)"
|
||||
option-label="bic"
|
||||
option-value="id"
|
||||
hide-selected
|
||||
|
|
|
@ -5,6 +5,7 @@ import FormModel from 'components/FormModel.vue';
|
|||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -30,31 +31,11 @@ const companySizes = [
|
|||
:rules="validate('supplier.nickname')"
|
||||
clearable
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('supplier.basicData.workerFk')"
|
||||
<VnSelectWorker
|
||||
v-model="data.workerFk"
|
||||
url="Workers/search"
|
||||
sort-by="nickname ASC"
|
||||
has-info="Responsible for approving invoices"
|
||||
:rules="validate('supplier.workerFk')"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>{{
|
||||
t('Responsible for approving invoices')
|
||||
}}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ scope.opt?.nickname }}, {{ scope.opt?.id }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('supplier.basicData.size')"
|
||||
v-model="data.companySize"
|
||||
|
@ -102,6 +83,5 @@ const companySizes = [
|
|||
|
||||
<i18n>
|
||||
es:
|
||||
Responsible for approving invoices: Responsable de aprobar las facturas
|
||||
Small(1-5), Medium(6-50), Big(> 50): Pequeño(1-5), Mediano(6-50), Grande(> 50)
|
||||
</i18n>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
|
@ -134,8 +134,6 @@ onMounted(() => {
|
|||
loadDefaultTicketAction();
|
||||
ticketHaveNegatives();
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
|
@ -168,8 +168,6 @@ const getTicketVolume = async () => {
|
|||
onMounted(() => {
|
||||
stateStore.rightDrawer = true;
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -9,6 +9,7 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
|||
import FetchData from 'components/FetchData.vue';
|
||||
|
||||
import { useState } from 'src/composables/useState';
|
||||
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
|
||||
|
||||
const emit = defineEmits(['onRequestCreated']);
|
||||
|
||||
|
@ -46,29 +47,7 @@ const onStateFkChange = (formData) => (formData.userFk = user.value.id);
|
|||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('expedition.worker')"
|
||||
v-model="data.userFk"
|
||||
url="Workers/search"
|
||||
fields=" ['id', 'name']"
|
||||
sort-by="name ASC"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
>
|
||||
<template #option="{ opt, itemProps }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ opt.name }}
|
||||
</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.nickname }}, {{ opt.code }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template></VnSelect
|
||||
>
|
||||
<VnSelectWorker v-model="data.userFk" :fields="['id', 'name']" />
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
import { computed, onMounted, ref, toRefs, watch } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
@ -24,6 +24,15 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
restoreTicket();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.ticket,
|
||||
() => restoreTicket
|
||||
);
|
||||
|
||||
const { push, currentRoute } = useRouter();
|
||||
const { dialog, notify } = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
@ -42,6 +51,7 @@ const hasPdf = ref();
|
|||
const weight = ref();
|
||||
const hasDocuwareFile = ref();
|
||||
const quasar = useQuasar();
|
||||
const canRestoreTicket = ref(false);
|
||||
const actions = {
|
||||
clone: async () => {
|
||||
const opts = { message: t('Ticket cloned'), type: 'positive' };
|
||||
|
@ -373,6 +383,54 @@ async function uploadDocuware(force) {
|
|||
|
||||
if (data) notify({ message: t('PDF sent!'), type: 'positive' });
|
||||
}
|
||||
|
||||
const restoreTicket = async () => {
|
||||
const filter = {
|
||||
fields: ['id', 'originFk', 'creationDate', 'newInstance'],
|
||||
where: {
|
||||
originFk: ticketId.value,
|
||||
newInstance: { like: '%"isDeleted":true%' },
|
||||
},
|
||||
order: 'creationDate DESC',
|
||||
limit: 1,
|
||||
};
|
||||
const params = { filter: JSON.stringify(filter) };
|
||||
|
||||
const { data } = await axios.get(`TicketLogs`, { params });
|
||||
|
||||
if (data && data.length) {
|
||||
const now = Date.vnNew();
|
||||
const maxDate = new Date(data[0].creationDate);
|
||||
maxDate.setHours(maxDate.getHours() + 1);
|
||||
if (now <= maxDate) {
|
||||
return (canRestoreTicket.value = true);
|
||||
}
|
||||
return (canRestoreTicket.value = false);
|
||||
}
|
||||
return (canRestoreTicket.value = false);
|
||||
};
|
||||
|
||||
async function openRestoreConfirmation(force) {
|
||||
if (!force)
|
||||
return quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Are you sure you want to restore the ticket?'),
|
||||
message: t('You are going to restore this ticket'),
|
||||
},
|
||||
})
|
||||
.onOk(async () => {
|
||||
ticketToRestore();
|
||||
});
|
||||
}
|
||||
|
||||
async function ticketToRestore() {
|
||||
const { data } = await axios.post(`Tickets/${ticketId.value}/restore`);
|
||||
if (data) {
|
||||
notify({ message: t('Ticket restored'), type: 'positive' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
|
@ -560,6 +618,12 @@ async function uploadDocuware(force) {
|
|||
</QItemSection>
|
||||
<QItemSection>{{ t('Show Proforma') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem v-if="canRestoreTicket" @click="openRestoreConfirmation()" v-ripple clickable>
|
||||
<QItemSection avatar>
|
||||
<QIcon name="restore" />
|
||||
</QItemSection>
|
||||
<QItemSection>{{ t('Restore ticket') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
v-if="isEditable"
|
||||
@click="showChangeTimeDialog = !showChangeTimeDialog"
|
||||
|
@ -746,4 +810,8 @@ es:
|
|||
You are going to delete this ticket: Vas a eliminar este ticket
|
||||
as PDF signed: como PDF firmado
|
||||
Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán?
|
||||
Restore ticket: Restaurar ticket
|
||||
Are you sure you want to restore the ticket?: ¿Seguro que quieres restaurar el ticket?
|
||||
You are going to restore this ticket: Vas a restaurar este ticket
|
||||
Ticket restored: Ticket restaurado
|
||||
</i18n>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, computed, onUnmounted } from 'vue';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
|
@ -210,8 +210,6 @@ onMounted(async () => {
|
|||
const filteredColumns = columns.value.filter(({ name }) => name !== 'history');
|
||||
robert marked this conversation as resolved
Outdated
|
||||
allColumnNames.value = filteredColumns.map(({ name }) => name);
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, computed, onUnmounted, watch } from 'vue';
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
@ -421,8 +421,6 @@ onMounted(async () => {
|
|||
getConfig();
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
|
||||
const items = ref([]);
|
||||
const newRow = ref({});
|
||||
|
||||
|
@ -770,7 +768,7 @@ watch(
|
|||
</template>
|
||||
<template #column-item="{ row }">
|
||||
<div class="row column full-width justify-between items-start">
|
||||
{{ row?.item?.name }}
|
||||
{{ row?.concept }}
|
||||
<div v-if="row?.item?.subName" class="subName">
|
||||
{{ row?.item?.subName.toUpperCase() }}
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ import VnInput from 'src/components/common/VnInput.vue';
|
|||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
import { toDateFormat } from 'src/filters/date';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { useAcl } from 'src/composables/useAcl';
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
|
||||
const emit = defineEmits(['updateDiscounts', 'getMana', 'refreshTable']);
|
||||
|
@ -48,7 +48,7 @@ const { push } = useRouter();
|
|||
const { t } = useI18n();
|
||||
const { dialog } = useQuasar();
|
||||
const { notify } = useNotify();
|
||||
const role = useRole();
|
||||
const acl = useAcl();
|
||||
const btnDropdownRef = ref(null);
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
|
||||
|
@ -58,8 +58,10 @@ const isClaimable = computed(() => {
|
|||
if (ticket.value) {
|
||||
const landedPlusWeek = new Date(ticket.value.landed);
|
||||
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
|
||||
const hasClaimManagerRole = role.hasAny('claimManager');
|
||||
return landedPlusWeek >= Date.vnNew() || hasClaimManagerRole;
|
||||
const createAfterDeadline = acl.hasAny([
|
||||
{ model: 'Claim', props: 'createAfterDeadline', accessType: 'WRITE' },
|
||||
]);
|
||||
return landedPlusWeek >= Date.vnNew() || createAfterDeadline;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Si solo usas name puedes usar la misma sintaxis del comentario anterior