Merge branch 'dev' into 8220-ItemsE2E
gitea/salix-front/pipeline/pr-dev This commit looks good
Details
gitea/salix-front/pipeline/pr-dev This commit looks good
Details
This commit is contained in:
commit
e42a2c8bce
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 🆕
|
||||
|
|
|
@ -1,20 +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';
|
||||
import { QForm } from 'quasar';
|
||||
import { QLayout } from 'quasar';
|
||||
import mainShortcutMixin from './mainShortcutMixin';
|
||||
|
||||
const { notify } = useNotify();
|
||||
import { useCau } from 'src/composables/useCau';
|
||||
|
||||
export default boot(({ app }) => {
|
||||
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;
|
||||
|
@ -45,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);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -25,7 +25,6 @@ const townsFetchDataRef = ref(false);
|
|||
const townFilter = ref({});
|
||||
|
||||
const countriesRef = ref(false);
|
||||
const provincesFetchDataRef = ref(false);
|
||||
const provincesOptions = ref([]);
|
||||
const townsOptions = ref([]);
|
||||
const town = ref({});
|
||||
|
@ -71,9 +70,6 @@ async function setProvince(id, data) {
|
|||
await fetchTowns();
|
||||
}
|
||||
async function onProvinceCreated(data) {
|
||||
await provincesFetchDataRef.value.fetch({
|
||||
where: { countryFk: postcodeFormData.countryFk },
|
||||
});
|
||||
postcodeFormData.provinceFk = data.id;
|
||||
}
|
||||
function provinceByCountry(countryFk = postcodeFormData.countryFk) {
|
||||
|
@ -92,7 +88,6 @@ function setTown(newTown, data) {
|
|||
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
|
||||
);
|
||||
|
@ -125,14 +120,6 @@ async function filterTowns(name) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
ref="provincesFetchDataRef"
|
||||
@on-fetch="handleProvinces"
|
||||
:sort-by="['name ASC']"
|
||||
:limit="30"
|
||||
auto-load
|
||||
url="Provinces/location"
|
||||
/>
|
||||
<FetchData
|
||||
ref="townsFetchDataRef"
|
||||
:sort-by="['name ASC']"
|
||||
|
@ -205,6 +192,11 @@ async function filterTowns(name) {
|
|||
: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-created="onProvinceCreated"
|
||||
required
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
|
@ -268,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;
|
||||
|
@ -286,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>
|
||||
|
|
|
@ -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
|
||||
:item="item"
|
||||
@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>
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
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 () => {
|
||||
{ customHTML: () => h(component, props) }
|
||||
),
|
||||
}).onOk(async () => {
|
||||
if (successFn) successFn();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -129,6 +129,7 @@ globals:
|
|||
small: Small
|
||||
medium: Medium
|
||||
big: Big
|
||||
email: Email
|
||||
pageTitles:
|
||||
logIn: Login
|
||||
addressEdit: Update address
|
||||
|
@ -329,6 +330,7 @@ globals:
|
|||
email: Email
|
||||
SSN: SSN
|
||||
fi: FI
|
||||
packing: ITP
|
||||
myTeam: My team
|
||||
departmentFk: Department
|
||||
countryFk: Country
|
||||
|
@ -369,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
|
||||
|
|
|
@ -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,7 @@ globals:
|
|||
SSN: NSS
|
||||
fi: NIF
|
||||
myTeam: Mi equipo
|
||||
packing: ITP
|
||||
countryFk: País
|
||||
changePass: Cambiar contraseña
|
||||
deleteConfirmTitle: Eliminar los elementos seleccionados
|
||||
|
@ -371,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
|
||||
|
@ -492,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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -101,7 +101,7 @@ const sumRisk = ({ clientRisks }) => {
|
|||
<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')"
|
||||
|
|
|
@ -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,7 +220,6 @@ function handleLocation(data, location) {
|
|||
</div>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<div class="col">
|
||||
<VnSelect
|
||||
:label="t('Incoterms')"
|
||||
:options="incoterms"
|
||||
|
@ -229,8 +228,6 @@ function handleLocation(data, location) {
|
|||
option-value="code"
|
||||
v-model="data.incotermsFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectDialog
|
||||
:label="t('Customs agent')"
|
||||
:options="customsAgents"
|
||||
|
@ -244,7 +241,14 @@ function handleLocation(data, location) {
|
|||
<CustomerNewCustomsAgent />
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</div>
|
||||
</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>
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -5,14 +5,26 @@ 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 { 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 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) {
|
||||
|
@ -33,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',
|
||||
},
|
||||
|
@ -59,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),
|
||||
|
@ -93,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',
|
||||
},
|
||||
{
|
||||
|
@ -108,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',
|
||||
},
|
||||
|
@ -131,9 +153,9 @@ const columns = computed(() => [
|
|||
},
|
||||
{
|
||||
label: t('lastEntries.supplier'),
|
||||
name: 'stems',
|
||||
name: 'supplier',
|
||||
field: 'supplier',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -157,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');
|
||||
|
@ -171,14 +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();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSubToolbar>
|
||||
<template #st-data>
|
||||
|
@ -187,27 +215,45 @@ onMounted(async () => {
|
|||
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">
|
||||
|
@ -229,8 +275,8 @@ onMounted(async () => {
|
|||
</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
|
||||
|
@ -249,7 +295,7 @@ onMounted(async () => {
|
|||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-cost="{ row }">
|
||||
<QTd @click.stop>
|
||||
<QTd @click.stop class="text-center">
|
||||
<span>
|
||||
{{ toCurrency(row.cost, 'EUR', 3) }}
|
||||
<QTooltip>
|
||||
|
@ -267,10 +313,25 @@ onMounted(async () => {
|
|||
</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%;
|
||||
|
@ -282,4 +343,10 @@ onMounted(async () => {
|
|||
padding: 0 11px;
|
||||
height: 28px;
|
||||
}
|
||||
.th :first-child {
|
||||
.td {
|
||||
text-align: center;
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -66,6 +66,7 @@ lastEntries:
|
|||
package: Package
|
||||
freight: Freight
|
||||
comission: Comission
|
||||
printedStickers: Pri.
|
||||
itemTags:
|
||||
removeTag: Remove tag
|
||||
addTag: Add tag
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -134,6 +134,7 @@ const getLocale = (label) => {
|
|||
/>
|
||||
</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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -75,19 +75,6 @@ watch(
|
|||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const onItemSaved = (updatedItem) => {
|
||||
requestAnimationFrame(() => {
|
||||
scrollToItem(updatedItem.items[0].itemFk);
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToItem = async (id) => {
|
||||
const element = itemRefs.value[id]?.$el;
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
};
|
||||
provide('onItemSaved', onItemSaved);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -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,12 +1,12 @@
|
|||
<script setup>
|
||||
import toCurrency from 'src/filters/toCurrency';
|
||||
import { inject, ref } from 'vue';
|
||||
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();
|
||||
|
@ -18,10 +18,17 @@ const props = defineProps({
|
|||
required: true,
|
||||
},
|
||||
});
|
||||
const onItemSaved = inject('onItemSaved');
|
||||
const state = useState();
|
||||
|
||||
const orderData = computed(() => state.get('orderData'));
|
||||
|
||||
const prices = ref((props.item.prices || []).map((item) => ({ ...item, quantity: 0 })));
|
||||
const descriptorData = useArrayData('orderData');
|
||||
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;
|
||||
|
@ -30,10 +37,19 @@ const addToOrder = async () => {
|
|||
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');
|
||||
await descriptorData.fetch({});
|
||||
onItemSaved({ ...props, items, saved: true });
|
||||
emit('added', items);
|
||||
emit('added', -totalQuantity(items));
|
||||
isLoading.value = false;
|
||||
};
|
||||
const canAddToOrder = () => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { useState } from 'src/composables/useState';
|
|||
import axios from 'axios';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import EditPictureForm from 'components/EditPictureForm.vue';
|
||||
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -143,10 +144,14 @@ const handlePhotoUpdated = (evt = false) => {
|
|||
:value="entity.user?.emailUser?.email"
|
||||
copy
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('worker.list.department')"
|
||||
:value="entity.department ? entity.department.department.name : null"
|
||||
<VnLv :label="t('worker.list.department')">
|
||||
<template #value>
|
||||
<span class="link" v-text="entity.department?.department?.name" />
|
||||
<DepartmentDescriptorProxy
|
||||
:id="entity.department?.department?.id"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :value="entity.phone">
|
||||
<template #label>
|
||||
{{ t('globals.phone') }}
|
||||
|
|
|
@ -108,7 +108,20 @@ const agencyOptions = ref([]);
|
|||
clearable
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
:label="t('Distribution point')"
|
||||
v-model="data.addressFk"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
url="Addresses"
|
||||
:fields="['id', 'nickname']"
|
||||
sort-by="id"
|
||||
hide-selected
|
||||
map-options
|
||||
:rules="validate('data.addressFk')"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInput
|
||||
v-model="data.inflation"
|
||||
|
@ -143,4 +156,5 @@ es:
|
|||
Inflation: Inflación
|
||||
Volumetric: Volumétrico
|
||||
Max length m³: Medida máxima tumbado
|
||||
Distribution point: Punto de distribución
|
||||
</i18n>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
describe('ItemLastEntries', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.login('buyer');
|
||||
cy.visit('/#/item/1/last-entries');
|
||||
cy.intercept('GET', /.*lastEntriesFilter/).as('item');
|
||||
cy.waitForElement('tbody');
|
||||
});
|
||||
|
||||
it('should filter by agency', () => {
|
||||
cy.get('tbody > tr')
|
||||
.its('length')
|
||||
.then((rowCount) => {
|
||||
cy.get('[data-cy="hideInventory"]').click();
|
||||
cy.wait('@item');
|
||||
cy.waitForElement('tbody');
|
||||
cy.get('tbody > tr').should('have.length.greaterThan', rowCount);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
describe('ZoneBasicData', () => {
|
||||
const notification = '.q-notification__message';
|
||||
const priceBasicData = '[data-cy="Price_input"]';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.viewport(1280, 720);
|
||||
|
@ -13,9 +14,15 @@ describe('ZoneBasicData', () => {
|
|||
cy.get(notification).should('contains.text', "can't be blank");
|
||||
});
|
||||
|
||||
it('should throw an error if the price is empty', () => {
|
||||
cy.get(priceBasicData).clear();
|
||||
cy.get('.q-btn-group > .q-btn--standard').click();
|
||||
cy.get(notification).should('contains.text', 'cannot be blank');
|
||||
});
|
||||
|
||||
it("should edit the basicData's zone", () => {
|
||||
cy.get('.q-card > :nth-child(1)').type(' modified');
|
||||
cy.get('.q-btn-group > .q-btn--standard').click();
|
||||
cy.get(notification).should('contains.text', 'Data saved');
|
||||
cy.checkNotification('Data saved');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
|
||||
import { createWrapper } from 'app/test/vitest/helper';
|
||||
import VnDiscount from 'components/common/vnDiscount.vue';
|
||||
|
||||
describe('VnDiscount', () => {
|
||||
let vm;
|
||||
|
||||
beforeAll(() => {
|
||||
vm = createWrapper(VnDiscount, {
|
||||
props: {
|
||||
data: {},
|
||||
price: 100,
|
||||
quantity: 2,
|
||||
discount: 10,
|
||||
}
|
||||
}).vm;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('total', () => {
|
||||
it('should calculate total correctly', () => {
|
||||
expect(vm.total).toBe(180);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue