Merge branch 'master' of https://gitea.verdnatura.es/verdnatura/salix-front into beta
gitea/salix-front/pipeline/head This commit looks good Details

This commit is contained in:
Alex Moreno 2025-01-07 07:50:30 +01:00
commit 37da6693f5
169 changed files with 2243 additions and 1381 deletions

View File

@ -1,3 +1,167 @@
# Version 24.52 - 2024-01-07
### Added 🆕
- chore: refs #8197 remove console log by:alexm
- chore: refs #8197 replace name by:alexm
- chore: refs #8197 unnecessary file by:alexm
- feat: #8110 apply mixin in quasar components by:Javier Segarra
- feat(Account & AccountRole): refs #8197 add VnCardMain by:alexm
- feat: addDptoLink by:Jtubau
- feat: added restore ticket function in ticket descriptor menu by:Jon
- feat: add support service wip by:jorgep
- feat: focus menu searchbar by:jorgep
- feat: make additional data object by:jorgep
- feat: message to grant access by:jorgep
- feat: refs #6583 add default param by:jorgep
- feat: refs #6583 add destination opt filter by:jorgep
- feat: refs #6583 add icon by:jorgep
- feat: refs #6583 add locale by:jorgep
- feat: refs #7072 added test to computed fn total by:Jtubau
- feat: refs #7235 update invoice out global form to fetch config based on serial type by:jgallego
- feat: refs #7301 add exclude inventory supplier from list by:pablone
- feat: refs #7301 enhance VnDateBadge styling and improve ItemLastEntries component by:pablone
- feat: refs #7882 Added distribution point by:guillermo
- feat: refs #7882 Added longitude & latitude by:guillermo
- feat: refs #7936 add autocomplete on tab fn by:jorgep
- feat: refs #7936 add company filter by:jorgep
- feat: refs #7936 add currency check before fetching by:jorgep
- feat: refs #7936 add dueDated field by:jorgep
- feat: refs #7936 add number validation to VnInputNumber & new daysAgo filter in InvoiceInFilter by:jorgep
- feat: refs #7936 add optionCaption by:jorgep
- feat: refs #7936 add row click navigation to InvoiceInSerial by:jorgep
- feat: refs #7936 add unit tests by:jorgep
- feat: refs #7936 add useAccountShortToStandard composable by:jorgep
- feat: refs #7936 calculate exchange & update taxable base by:jorgep
- feat: refs #7936 enhance downloadFile function to support opening in a new tab by:jorgep
- feat: refs #7936 enhance getTotal fn & add unit tests by:jorgep
- feat: refs #7936 enhance vn-select by:jorgep
- feat: refs #7936 improve optionLabel logic in InvoiceInVat component for better handling of numeric values by:jorgep
- feat: refs #7936 limit decimal places by:jorgep
- feat: refs #7936 make fields required by:jorgep
- feat: refs #7936 show country code & isVies fields by:jorgep
- feat: refs #7936 show id & value by:jorgep
- feat: refs #7936 simplify optionLabel wip by:jorgep
- feat: refs #7936 update 'isVies' label to use global translation key by:jorgep
- feat: refs #7936 update option labels in InvoiceIn components for better clarity by:jorgep
- feat: refs #7936 use default invoice data by:jorgep
- feat: refs #8001 change request by:robert
- feat: refs #8001 ticketExpeditionGrafana by:robert
- feat: refs #8194 created VnSelectWorker component and use it in Lilium by:Jon
- feat: refs #8197 better leftMenu and VnCardMain improvements by:alexm
- feat: refs #8197 default leftMenu by:alexm
- feat: refs #8197 default sectionName by:alexm
- feat: refs #8197 keepData in VnSection by:alexm
- feat: refs #8197 vnTableFilter by:alexm
- feat: refs #8197 working rightMenu by:alexm
- feat: remove re-fetch when add element by:Javier Segarra
- feat: remove search after category by:Javier Segarra
- feat: requested changes in item module by:Jon
- feat: update quantity by:Javier Segarra
- feat(VnPaginate): refs #8197 hold data when change to Card by:alexm
### Changed 📦
- perf: #6896 REMOVE COMMENTS by:Javier Segarra
- perf: qFormMixin by:Javier Segarra
- perf: qFormMixin improvement by:Javier Segarra
- perf: refs #8194 select worker component by:Jon
- perf: refs #8197 perf by:alexm
- perf: remove comments by:Javier Segarra
- perf: remove unused variables (origin/warmfix_noUsedVars) by:Javier Segarra
- refactor: added again search emit by:Jon
- refactor: add useCau composable by:jorgep
- refactor: deleted log by:Jon
- refactor: deleted onUnmounted code by:Jon
- refactor: deleted useless hidden tag by:Jon
- refactor: deleted warnings and corrected itemTag by:Jon
- refactor: drop logic by:jorgep
- refactor: ignore params when searching by id on searchbar (origin/VnSearchbar-SearchRemoveParams) by:Jon
- refactor: log error by:Jon
- refactor: refs #7936 locale by:jorgep
- refactor: refs #7936 simplify getTotal fn by:jorgep
- refactor: refs #7936 update label capitalization and replace invoice type options by:jorgep
- refactor: refs #8194 deleted unnecessary label by:Jon
- refactor: refs #8194 modified select worker template by:Jon
- refactor: refs #8194 modified select worker to allow no one filter from monitor ticket by:Jon
- refactor: refs #8194 moved translation to the correct place by:Jon
- refactor: refs #8194 requested changes by:Jon
- refactor: refs #8194 structure changes in component and related files by:Jon
- refactor: refs #8197 adapt AccountAcls to VnCardMain by:alexm
- refactor: refs #8197 adapt AccountAlias by:alexm
- refactor: refs #8197 adapt Ticket to VnCardMain by:alexm
- refactor: refs #8197 backward compatible (8197-VnCardMain_backwardCompatibility) by:alexm
- refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection by:alexm
- refactor: refs #8288 changed invoice out spanish translation by:provira
- refactor: use locale keys by:jorgep
- refactor: use teleport to avoid qdrawer overlapping by:Jon
- refactor: use VnSelectWorker by:Jon
### Fixed 🛠️
- fix: account by:carlossa
- fix: account create by:carlossa
- fix: accountList create by:carlossa
- fix(AccountList): use $refs by:alexm
- fix: add data-key by:alexm
- fix: addLocales by:Jtubau
- fix: dated field by:Jon
- fix: e2e by:jorgep
- fix: fix department filter by:carlossa
- fix: fixed translations by:Javier Segarra
- fix: fixed translations by:provira
- fix: get total from api by:Javier Segarra
- fix: handle non-object options by:jorgep
- fix: monitorPayMethodFilter by:carlossa
- fix: orderBy priority by:Javier Segarra
- fix: prevent null by:jorgep
- fix: redirection vnTable VnTableFilter by:alexm
- fix: refs #6389 fix filter trad by:carlossa
- fix: refs #6389 fix front, filters, itp by:carlossa
- fix: refs #6389 front add packing filter by:carlossa
- fix: refs #6389 front by:carlossa
- fix: refs #6389 front filters by:carlossa
- fix: refs #6389 ipt by:carlossa
- fix: refs #6389 packing by:carlossa
- fix: refs #6583 update checkbox for filtering by destination in TicketAdvanceFilter by:jorgep
- fix: refs #7031 add test e2e by:carlossa
- fix: refs #7031 fix zoneTest by:carlossa
- fix: refs #7301 unnecessary console logs from ItemLastEntries.vue by:pablone
- fix: refs #7936 changes by:jorgep
- fix: refs #7936 decimal places & locale by:jorgep
- fix: refs #7936 descriptor & dueday by:jorgep
- fix: refs #7936 exclude disabled els on tab by:jorgep
- fix: refs #7936 format tax calculation to two decimal places by:jorgep
- fix: refs #7936 improve error handling by:jorgep
- fix: refs #7936 redirection by:jorgep
- fix: refs #7936 rollback by:jorgep
- fix: refs #7936 serial by:jorgep
- fix: refs #7936 tabulation wip by:jorgep
- fix: refs #7936 test by:jorgep
- fix: refs #8114 clean by:carlossa
- fix: refs #8114 fix agencyList by:carlossa
- fix: refs #8114 fix lifeCycle hooks by:carlossa
- fix: refs #8114 fix pr by:carlossa
- fix: refs #8114 fix removeAddress by:carlossa
- fix: refs #8114 orderList by:carlossa
- fix: refs #8114 remove logs by:carlossa
- fix: refs #8197 mapKey (origin/8197-perf_vnTableInside, 8197-perf_vnTableInside) by:alexm
- fix: refs #8197 redirection (8197-perf_redirection) by:alexm
- fix: refs #8197 staticParams and redirect by:alexm
- fix: refs #8197 vnPaginate onFetch emit by:alexm
- fix: refs #8197 vnPaginate when change :id by:alexm
- fix: refs #8197 vnTableFilter in vnTable by:alexm
- fix: refs #8315 ticketBoxing test by:alexm
- fix: remove url by:carlossa
- fix: rollback by:jorgep
- fix: test by:jorgep
- fix(VnDmsList): refs #8197 add mapKey by:alexm
- revert: refs #8197 arrayData changes by:alexm
- test: refs #8197 fix e2e by:alexm
- test: refs #8315 fix claimDevelopment fixtures by:alexm
- test: refs #8315 fix clientList by:alexm
- test: refs #8315 fix VnSelect in e2e by:alexm
# Version 24.50 - 2024-12-10
### Added 🆕

View File

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

View File

@ -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);
},
};

View File

@ -1,14 +1,37 @@
import { getCurrentInstance } from 'vue';
function focusFirstInput(input) {
input.focus();
}
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' && !that.$attrs['prevent-submit']) {
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();
@ -24,7 +47,5 @@ export default {
that.onSubmit();
}
});
}
}
},
};

View File

@ -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);
};
});

View File

@ -55,13 +55,6 @@ async function setCountry(countryFk, data) {
}
// 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);

View File

@ -187,6 +187,7 @@ function normalize(text) {
class="full-width"
filled
dense
autofocus
/>
</QItem>
<QSeparator />

View File

@ -313,7 +313,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
show-if-above
>
<QScrollArea class="fit">
<VnTableFilter :data-key="$attrs['data-key']" :columns="columns" />
<VnTableFilter :data-key="$attrs['data-key']" :columns="columns" :redirect="redirect" />
</QScrollArea>
</QDrawer>
<CrudModel
@ -491,6 +491,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
$props.rowClick && $props.rowClick(row);
}
"
style="height: 100%"
>
<QCardSection
vertical

View File

@ -14,6 +14,7 @@ defineProps({
hide-dropdown-icon
focus-on-mount
@update:model-value="promise"
data-cy="vnBtnSelect_select"
/>
</QBtnDropdown>
</template>

View File

@ -0,0 +1,39 @@
<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', class: 'black-text-color' };
if (timeDiff < 0) return { color: 'success', class: 'black-text-color' };
return { color: 'transparent', class: 'normal-text-color' };
}
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>
<style lang="scss">
.black-text-color {
color: var(--vn-black-text-color);
}
.normal-text-color {
color: var(--vn-text-color);
}
</style>

View File

@ -297,7 +297,7 @@ defineExpose({
ref="dmsRef"
:data-key="$props.model"
:url="$props.model"
:filter="dmsFilter"
:user-filter="dmsFilter"
:order="['dmsFk DESC']"
:auto-load="true"
@on-fetch="setData"

View File

@ -1,13 +1,28 @@
<script setup>
import VnInput from 'src/components/common/VnInput.vue';
import { ref } from 'vue';
import { useAttrs } from 'vue';
defineProps({
step: { type: Number, default: 0.01 },
decimalPlaces: { type: Number, default: 2 },
positive: { type: Boolean, default: true },
});
const model = defineModel({ type: [Number, String] });
const $attrs = useAttrs();
const step = ref($attrs.step || 0.01);
</script>
<template>
<VnInput v-bind="$attrs" v-model.number="model" type="number" :step="step" />
<VnInput
v-bind="$attrs"
v-model.number="model"
type="number"
:step="step"
@input="
(evt) => {
const val = evt.target.value;
if (positive && val < 0) return (model = 0);
const [, decimal] = val.split('.');
if (val && decimal?.length > decimalPlaces)
model = parseFloat(val).toFixed(decimalPlaces);
}
"
/>
</template>

View File

@ -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);
}

View File

@ -20,16 +20,15 @@ const hasContent = ref();
let observer;
onMounted(() => {
if (teleportRef.value) {
if (!teleportRef.value) return;
const checkContent = () => {
hasContent.value = teleportRef.value.innerHTML.trim() !== '';
hasContent.value = teleportRef.value?.innerHTML?.trim() !== '';
};
observer = new MutationObserver(checkContent);
observer.observe(teleportRef.value, { childList: true, subtree: true });
checkContent();
}
});
</script>

View File

@ -4,11 +4,12 @@ import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnTableFilter from '../VnTable/VnTableFilter.vue';
import { onBeforeMount, computed } from 'vue';
import { useArrayData } from 'src/composables/useArrayData';
import { useRoute } from 'vue-router';
const $props = defineProps({
section: {
type: String,
required: true,
default: null,
},
dataKey: {
type: String,
@ -38,14 +39,28 @@ const $props = defineProps({
type: Boolean,
default: true,
},
keepData: {
type: Boolean,
default: true,
},
});
const sectionValue = computed(() => $props.section ?? $props.dataKey);
const route = useRoute();
let arrayData;
const sectionValue = computed(() => $props.section ?? $props.dataKey);
const isMainSection = computed(() => {
const isSame = sectionValue.value == route.name;
if (!isSame && arrayData) {
arrayData.reset(['userParams', 'userFilter']);
}
return isSame;
});
onBeforeMount(() => {
if ($props.dataKey)
arrayData = useArrayData($props.dataKey, {
searchUrl: 'table',
keepData: $props.keepData,
...$props.arrayDataProps,
navigate: $props.redirect,
});
@ -74,6 +89,6 @@ onBeforeMount(() => {
</slot>
</template>
</RightMenu>
<slot name="body" v-if="sectionValue == $route.name" />
<slot name="body" v-if="isMainSection" />
<RouterView v-else />
</template>

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired';
import dataByOrder from 'src/utils/dataByOrder';
import { QItemLabel } from 'quasar';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
const $attrs = useAttrs();
@ -33,6 +34,10 @@ const $props = defineProps({
type: String,
default: 'id',
},
optionCaption: {
type: String,
default: null,
},
optionFilter: {
type: String,
default: null,
@ -101,6 +106,10 @@ const $props = defineProps({
type: String,
default: null,
},
isOutlined: {
type: Boolean,
default: false,
},
});
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -115,6 +124,15 @@ const noOneOpt = ref({
[optionValue.value]: false,
[optionLabel.value]: noOneText,
});
const styleAttrs = computed(() => {
return $props.isOutlined
? {
dense: true,
outlined: true,
rounded: true,
}
: {};
});
const isLoading = ref(false);
const useURL = computed(() => $props.url);
const value = computed({
@ -292,7 +310,7 @@ function handleKeyDown(event) {
}
const focusableElements = document.querySelectorAll(
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'
);
const currentIndex = Array.prototype.indexOf.call(
focusableElements,
@ -311,9 +329,8 @@ function handleKeyDown(event) {
:options="myOptions"
:option-label="optionLabel"
:option-value="optionValue"
v-bind="$attrs"
v-bind="{ ...$attrs, ...styleAttrs }"
@filter="filterHandler"
@keydown="handleKeyDown"
:emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])"
@ -328,13 +345,15 @@ function handleKeyDown(event) {
:input-debounce="useURL ? '300' : '0'"
:loading="isLoading"
@virtual-scroll="onScroll"
@keydown="handleKeyDown"
:data-cy="$attrs.dataCy ?? $attrs.label + '_select'"
:data-url="url"
>
<template #append>
<QIcon
v-show="isClearable && value"
name="close"
@click.stop="
@click="
() => {
value = null;
emit('remove');
@ -362,6 +381,22 @@ function handleKeyDown(event) {
</div>
<slot v-else :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection v-if="typeof opt !== 'object'"> {{ opt }}</QItemSection>
<QItemSection v-else-if="opt[optionValue] == opt[optionLabel]">
<QItemLabel>{{ opt[optionLabel] }}</QItemLabel>
</QItemSection>
<QItemSection v-else>
<QItemLabel>
{{ opt[optionLabel] }}
</QItemLabel>
<QItemLabel caption v-if="optionCaption !== false">
{{ `#${opt[optionCaption] || opt[optionValue]}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</QSelect>
</template>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -98,6 +98,7 @@ function cancel() {
/>
<QBtn
:label="t('globals.confirm')"
:title="t('globals.confirm')"
color="primary"
:loading="isLoading"
@click="confirm()"

View File

@ -5,8 +5,10 @@ import { useArrayData } from 'composables/useArrayData';
import toDate from 'filters/toDate';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import { useFilterParams } from 'src/composables/useFilterParams';
import { useRoute } from 'vue-router';
const { t } = useI18n();
const { t, te } = useI18n();
const route = useRoute();
const $props = defineProps({
modelValue: {
type: Object,
@ -65,6 +67,7 @@ const emit = defineEmits([
'update:modelValue',
'refresh',
'clear',
'search',
'init',
'remove',
'setUserParams',
@ -181,6 +184,14 @@ function formatValue(value) {
return `"${value}"`;
}
const getLocale = (label) => {
const param = label.split('.').at(-1);
const globalLocale = `globals.params.${param}`;
if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`)));
else return t(`${route.meta.moduleName}.params.${param}`);
};
</script>
<template>
@ -191,7 +202,11 @@ function formatValue(value) {
style="position: fixed; z-index: 1; right: 0; bottom: 0"
icon="search"
@click="search()"
></QBtn>
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.search') }}
</QTooltip>
</QBtn>
<QForm @submit="search" id="filterPanelForm" @keyup.enter="search()">
<QList dense>
<QItem class="q-mt-xs">
@ -230,7 +245,12 @@ function formatValue(value) {
@remove="remove(chip.label)"
data-cy="vnFilterPanelChip"
>
<slot name="tags" :tag="chip" :format-fn="formatValue">
<slot
name="tags"
:tag="chip"
:format-fn="formatValue"
:get-locale="getLocale"
>
<div class="q-gutter-x-xs">
<strong>{{ chip.label }}:</strong>
<span>"{{ formatValue(chip.value) }}"</span>
@ -243,6 +263,7 @@ function formatValue(value) {
:params="userParams"
:tags="customTags"
:format-fn="formatValue"
:get-locale="getLocale"
:search-fn="search"
/>
</div>
@ -252,6 +273,7 @@ function formatValue(value) {
<QList dense class="list q-gutter-y-sm q-mt-sm">
<slot
name="body"
:get-locale="getLocale"
:params="userParams"
:orders="userOrders"
:search-fn="search"

View File

@ -111,6 +111,7 @@ onMounted(async () => {
});
onBeforeUnmount(() => {
if (!store.keepData) arrayData.reset(['data']);
arrayData.resetPagination();
});

View File

@ -126,6 +126,7 @@ async function search() {
delete filter.params.search;
}
await arrayData.applyFilter(filter);
searchText.value = undefined;
}
</script>
<template>

View File

@ -0,0 +1,16 @@
import axios from 'axios';
export async function getExchange(amount, currencyFk, dated, decimalPlaces = 2) {
try {
const { data } = await axios.get('ReferenceRates/findOne', {
params: {
filter: {
fields: ['value'],
where: { currencyFk, dated },
},
},
});
return (amount / data.value).toFixed(decimalPlaces);
} catch (e) {
return null;
}
}

View File

@ -1,10 +1,10 @@
import { toCurrency } from 'src/filters';
export function getTotal(rows, key, opts = {}) {
const { currency, cb } = opts;
const { currency, cb, decimalPlaces } = opts;
const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0);
return currency
? toCurrency(total, currency == 'default' ? undefined : currency)
: total;
: parseFloat(total).toFixed(decimalPlaces ?? 2);
}

View File

@ -0,0 +1,4 @@
export function useAccountShortToStandard(val) {
if (!val || !/^\d+(\.\d*)$/.test(val)) return;
return val?.replace('.', '0'.repeat(11 - val.length));
}

View File

@ -53,6 +53,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
'searchUrl',
'navigate',
'mapKey',
'keepData',
];
if (typeof userOptions === 'object') {
for (const option in userOptions) {
@ -302,7 +303,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
for (const row of data) {
const key = row[store.mapKey];
const val = { ...row, key };
if (store.map.has(key)) {
if (key && store.map.has(key)) {
const { position } = store.map.get(key);
val.position = position;
store.map.set(key, val);

73
src/composables/useCau.js Normal file
View File

@ -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);
}

View File

@ -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,
});
};

View File

@ -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();
});
};

View File

@ -3,19 +3,21 @@
@import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass';
body.body--light {
--font-color: black;
--vn-header-color: #cecece;
--vn-page-color: #ffffff;
--vn-section-color: #e0e0e0;
--vn-section-hover-color: #b9b9b9;
--vn-text-color: var(--font-color);
--vn-text-color: black;
--vn-label-color: #5f5f5f;
--vn-accent-color: #e7e3e3;
--vn-empty-tag: #acacac;
--vn-black-text-color: black;
--vn-text-color-contrast: white;
background-color: var(--vn-page-color);
.q-header .q-toolbar {
color: var(--font-color);
color: var(--vn-text-color);
}
}
body.body--dark {
@ -26,6 +28,9 @@ body.body--dark {
--vn-text-color: white;
--vn-label-color: #a8a8a8;
--vn-accent-color: #424242;
--vn-empty-tag: #2d2d2d;
--vn-black-text-color: black;
--vn-text-color-contrast: black;
background-color: var(--vn-page-color);
}
@ -84,6 +89,10 @@ select:-webkit-autofill {
background-color: var(--vn-section-hover-color);
}
.bg-vn-page {
background-color: var(--vn-page-color);
}
.color-vn-label {
color: var(--vn-label-color);
}
@ -187,7 +196,7 @@ select:-webkit-autofill {
.q-tooltip {
background-color: var(--vn-page-color);
color: var(--font-color);
color: var(--vn-text-color);
font-size: medium;
}

View File

@ -129,6 +129,7 @@ globals:
small: Small
medium: Medium
big: Big
email: Email
pageTitles:
logIn: Login
addressEdit: Update address
@ -329,13 +330,26 @@ globals:
email: Email
SSN: SSN
fi: FI
packing: ITP
myTeam: My team
departmentFk: Department
from: From
to: To
supplierFk: Supplier
supplierRef: Supplier ref
serial: Serial
amount: Importe
awbCode: AWB
correctedFk: Rectified
correctingFk: Rectificative
daysOnward: Days onward
countryFk: Country
companyFk: Company
changePass: Change password
deleteConfirmTitle: Delete selected elements
changeState: Change state
raid: 'Raid {daysInForward} days'
isVies: Vies
errors:
statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred
@ -369,6 +383,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
@ -398,8 +417,8 @@ entry:
buys: Buys
stickers: Stickers
package: Package
packing: Packing
grouping: Grouping
packing: Pack.
grouping: Group.
buyingValue: Buying value
import: Import
pvp: PVP
@ -592,7 +611,7 @@ worker:
fi: DNI/NIE/NIF
birth: Birth
isFreelance: Freelance
isSsDiscounted: Bonificación SS
isSsDiscounted: SS Bonification
hasMachineryAuthorized: Machinery authorized
isDisable: Disable
notificationsManager:
@ -724,7 +743,6 @@ supplier:
sageTransactionTypeFk: Sage transaction type
supplierActivityFk: Supplier activity
isTrucker: Trucker
isVies: Vies
billingData:
payMethodFk: Billing data
payDemFk: Payment deadline
@ -839,6 +857,7 @@ components:
value: Value
# ItemFixedPriceFilter
buyerFk: Buyer
warehouseFk: Warehouse
started: From
ended: To
mine: For me

View File

@ -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,11 +336,22 @@ globals:
SSN: NSS
fi: NIF
myTeam: Mi equipo
from: Desde
to: Hasta
supplierFk: Proveedor
supplierRef: Ref. proveedor
serial: Serie
amount: Importe
awbCode: AWB
daysOnward: Días adelante
packing: ITP
countryFk: País
companyFk: Empresa
changePass: Cambiar contraseña
deleteConfirmTitle: Eliminar los elementos seleccionados
changeState: Cambiar estado
raid: 'Redada {daysInForward} días'
isVies: Vies
errors:
statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor
@ -371,6 +383,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
@ -401,8 +418,8 @@ entry:
buys: Compras
stickers: Etiquetas
package: Embalaje
packing: Packing
grouping: Grouping
packing: Pack.
grouping: Group.
buyingValue: Coste
import: Importe
pvp: PVP
@ -492,7 +509,7 @@ invoiceOut:
ticketList: Listado de tickets
summary:
issued: Fecha
dued: Vencimiento
dued: Fecha límite
booked: Contabilizada
taxBreakdown: Desglose impositivo
taxableBase: Base imp.
@ -591,6 +608,15 @@ worker:
role: Rol
sipExtension: Extensión
locker: Taquilla
fiDueDate: F. caducidad DNI
sex: Sexo
seniority: Antigüedad
fi: DNI/NIE/NIF
birth: F. nacimiento
isFreelance: Autónomo
isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para maquinaria
isDisable: Deshabilitado
notificationsManager:
activeNotifications: Notificaciones activas
availableNotifications: Notificaciones disponibles
@ -719,7 +745,6 @@ supplier:
sageTransactionTypeFk: Tipo de transacción sage
supplierActivityFk: Actividad proveedor
isTrucker: Transportista
isVies: Vies
billingData:
payMethodFk: Forma de pago
payDemFk: Plazo de pago
@ -833,6 +858,7 @@ components:
value: Valor
# ItemFixedPriceFilter
buyerFk: Comprador
warehouseFk: Almacen
started: Desde
ended: Hasta
mine: Para mi

View File

@ -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>

View File

@ -31,7 +31,6 @@ const rolesOptions = ref([]);
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
:hidden-tags="['search']"
:redirect="false"
search-url="table"
>

View File

@ -4,6 +4,7 @@ import { computed, ref } from 'vue';
import VnTable from 'components/VnTable/VnTable.vue';
import AccountSummary from './Card/AccountSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import VnInput from 'src/components/common/VnInput.vue';
import VnSection from 'src/components/common/VnSection.vue';
import FetchData from 'src/components/FetchData.vue';
@ -23,10 +24,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',
@ -36,7 +54,11 @@ const columns = computed(() => [
optionLabel: 'name',
},
},
columnField: {
component: null,
},
format: ({ role }, dashIfEmpty) => dashIfEmpty(role?.name),
create: true,
},
{
align: 'left',
@ -52,20 +74,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,
},
@ -103,7 +137,6 @@ function exprBuilder(param, value) {
}
}
</script>
<template>
<FetchData url="VnRoles" @on-fetch="(data) => (roles = data)" auto-load />
<VnSection
@ -119,13 +152,32 @@ function exprBuilder(param, value) {
>
<template #body>
<VnTable
ref="tableRef"
:data-key="dataKey"
:columns="columns"
:create="{
urlCreate: 'VnUsers',
title: t('Create user'),
onDataSaved: ({ id }) => $refs.tableRef.redirect(id),
formInitialData: {},
}"
default-mode="table"
redirect="account"
:use-model="true"
:right-search="false"
>
<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>
</VnSection>
</template>
@ -135,4 +187,7 @@ function exprBuilder(param, value) {
Id: Id
Nickname: Nickname
Name: Nombre
Password: Contraseña
Active: Activo
Role: Rol
</i18n>

View File

@ -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>

View File

@ -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>

View File

@ -50,7 +50,7 @@ const removeRole = async () => {
>
<template #menu>
<QItem v-ripple clickable @click="removeRole()">
<QItemSection>{{ t('Delete') }}</QItemSection>
<QItemSection>{{ t('globals.delete') }}</QItemSection>
</QItem>
</template>
<template #body="{ entity }">

View File

@ -6,6 +6,7 @@ import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const route = useRoute();
@ -157,19 +158,14 @@ const columns = computed(() => [
auto-width
@keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
>
<VnSelect
<VnSelectWorker
v-if="col.name == 'worker'"
v-model="row[col.model]"
:url="col.url"
:where="col.where"
:sort-by="col.sortBy"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
>
<template #option="scope" v-if="col.name == 'worker'">
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
@ -180,7 +176,20 @@ const columns = computed(() => [
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnSelectWorker>
<VnSelect
v-else
v-model="row[col.model]"
:url="col.url"
:where="col.where"
:sort-by="col.sortBy"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
/>
</QTd>
</template>
<template #item="props">

View File

@ -120,13 +120,13 @@ const developmentColumns = ref([
{
name: 'claimReason',
label: 'claim.reason',
field: (row) => row.claimReason.description,
field: (row) => row.claimReason?.description,
sortable: true,
},
{
name: 'claimResult',
label: 'claim.result',
field: (row) => row.claimResult.description,
field: (row) => row.claimResult?.description,
sortable: true,
},
{

View File

@ -131,7 +131,7 @@ const STATE_COLOR = {
<VnTable
data-key="ClaimList"
url="Claims/filter"
:order="['priority ASC', 'created ASC']"
:order="['t.priority ASC', 'created ASC']"
:columns="columns"
redirect="claim"
:right-search="false"

View File

@ -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"

View File

@ -110,7 +110,7 @@ function handleLocation(data, location) {
<VnRow>
<QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" />
<div>
<QCheckbox :label="t('Vies')" v-model="data.isVies" />
<QCheckbox :label="t('globals.isVies')" v-model="data.isVies" />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip>
{{ t('whenActivatingIt') }}
@ -169,7 +169,6 @@ es:
Active: Activo
Frozen: Congelado
Has to invoice: Factura
Vies: Vies
Notify by email: Notificar vía e-mail
Invoice by address: Facturar por consignatario
Is equalizated: Recargo de equivalencia

View File

@ -63,9 +63,10 @@ const columns = computed(() => [
<template>
<QPage class="column items-center q-pa-md">
<VnTable
data-key="Mandates"
url="Mandates"
:filter="filter"
auto-load
url="Mandates"
:columns="columns"
class="full-width q-mt-md"
:right-search="false"

View File

@ -173,7 +173,7 @@ const sumRisk = ({ clientRisks }) => {
:label="t('customer.summary.notifyByEmail')"
:value="entity.isToBeMailed"
/>
<VnLv :label="t('customer.summary.vies')" :value="entity.isVies" />
<VnLv :label="t('globals.isVies')" :value="entity.isVies" />
</VnRow>
</QCard>
<QCard class="vn-one">

View File

@ -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">
@ -185,6 +170,8 @@ en:
phone: Phone
email: Email
zoneFk: Zone
socialName : Social name
name: Name
postcode: Postcode
es:
params:
@ -196,6 +183,8 @@ es:
phone: Teléfono
email: Email
zoneFk: Zona
socialName : Razón social
name: Nombre
postcode: CP
FI: NIF
Salesperson: Comercial

View File

@ -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,7 +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 VnAvatar from 'src/components/ui/VnAvatar.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const router = useRouter();
@ -264,7 +263,7 @@ const columns = computed(() => [
},
{
align: 'left',
label: t('customer.extendedList.tableVisibleColumns.isVies'),
label: t('globals.isVies'),
name: 'isVies',
columnFilter: {
inWhere: true,
@ -421,40 +420,17 @@ function handleLocation(data, location) {
:right-search="false"
>
<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"

View File

@ -240,6 +240,7 @@ es:
defaulterSinced: Desde
Client: Cliente
Salesperson: Comercial
Departments: Departamentos
Country: País
P. Method: F. Pago
Balance D.: Saldo V.

View File

@ -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>

View File

@ -88,7 +88,6 @@ customer:
businessTypeFk: Business type
sageTaxTypeFk: Sage tax type
sageTransactionTypeFk: Sage tr. type
isVies: Vies
isTaxDataChecked: Verified data
isFreezed: Freezed
hasToInvoice: Invoice

View File

@ -90,7 +90,6 @@ customer:
businessTypeFk: Tipo de negocio
sageTaxTypeFk: Tipo de impuesto Sage
sageTransactionTypeFk: Tipo tr. sage
isVies: Vies
isTaxDataChecked: Datos comprobados
isFreezed: Congelado
hasToInvoice: Factura

View File

@ -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

View File

@ -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"

View File

@ -58,7 +58,7 @@ onMounted(async () => {
dash
/>
<VnLv
:label="t('department.email')"
:label="t('globals.email')"
:value="department.notificationEmail"
dash
/>

View File

@ -74,8 +74,8 @@ const showEntryReport = () => {
</template>
<template #body="{ entity }">
<VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" />
<VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" />
<VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" />
<VnLv :label="t('globals.shipped')" :value="toDate(entity.travel?.shipped)" />
<VnLv :label="t('globals.landed')" :value="toDate(entity.travel?.landed)" />
<VnLv
:label="t('globals.warehouseOut')"
:value="entity.travel?.warehouseOut?.name"

View File

@ -212,7 +212,7 @@ const fetchEntryBuys = async () => {
:label="t('entry.summary.travelAgency')"
:value="entry.travel.agency?.name"
/>
<VnLv :label="t('shipped')" :value="toDate(entry.travel.shipped)" />
<VnLv :label="t('globals.shipped')" :value="toDate(entry.travel.shipped)" />
<VnLv
:label="t('globals.warehouseOut')"
:value="entry.travel.warehouseOut?.name"
@ -222,7 +222,7 @@ const fetchEntryBuys = async () => {
v-model="entry.travel.isDelivered"
:disable="true"
/>
<VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" />
<VnLv :label="t('globals.landed')" :value="toDate(entry.travel.landed)" />
<VnLv
:label="t('globals.warehouseIn')"
:value="entry.travel.warehouseIn?.name"

View File

@ -58,7 +58,7 @@ const tagValues = ref([]);
<QItem class="q-my-md">
<QItemSection>
<VnSelect
:label="t('components.itemsFilterPanel.supplierFk')"
:label="t('globals.params.supplierFk')"
v-model="params.supplierFk"
:options="suppliersOptions"
option-value="id"
@ -85,7 +85,7 @@ const tagValues = ref([]);
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('components.itemsFilterPanel.from')"
:label="t('components.itemsFilterPanel.started')"
v-model="params.from"
is-outlined
@update:model-value="searchFn()"
@ -95,7 +95,7 @@ const tagValues = ref([]);
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('components.itemsFilterPanel.to')"
:label="t('components.itemsFilterPanel.ended')"
v-model="params.to"
is-outlined
@update:model-value="searchFn()"
@ -113,7 +113,7 @@ const tagValues = ref([]);
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('components.itemsFilterPanel.visible')"
:label="t('globals.visible')"
v-model="params.visible"
toggle-indeterminate
@update:model-value="searchFn()"

View File

@ -65,6 +65,6 @@ onMounted(async () => {
es:
Date: Fecha
params:
dated: Date
dated: Fecha
workerFk: Trabajador
</i18n>

View File

@ -116,6 +116,7 @@ function deleteFile(dmsFk) {
<template #form="{ data }">
<VnRow>
<VnSelect
:required="true"
:label="t('supplierFk')"
v-model="data.supplierFk"
option-value="id"
@ -244,14 +245,19 @@ function deleteFile(dmsFk) {
</VnRow>
<VnRow>
<VnSelect
:required="true"
:is-clearable="false"
:label="t('Currency')"
v-model="data.currencyFk"
:options="currencies"
option-value="id"
option-label="code"
sort-by="id"
/>
<VnSelect
:required="true"
:is-clearable="false"
v-if="companiesRef"
:label="t('Company')"
v-model="data.companyFk"
@ -262,7 +268,7 @@ function deleteFile(dmsFk) {
</VnRow>
<VnRow>
<VnSelect
:label="t('invoiceIn.summary.sage')"
:label="t('InvoiceIn.summary.sage')"
v-model="data.withholdingSageFk"
:options="sageWithholdings"
option-value="id"

View File

@ -1,23 +1,21 @@
<script setup>
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { ref, computed, capitalize } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData';
import { useCapitalize } from 'src/composables/useCapitalize';
import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { push, currentRoute } = useRouter();
const route = useRoute();
const { t } = useI18n();
const invoiceId = +currentRoute.value.params.id;
const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref();
const filter = {
include: { relation: 'invoiceIn' },
where: { correctingFk: invoiceId },
where: { correctingFk: route.params.id },
};
const columns = computed(() => [
{
@ -31,7 +29,7 @@ const columns = computed(() => [
},
{
name: 'type',
label: useCapitalize(t('globals.type')),
label: capitalize(t('globals.type')),
field: (row) => row.cplusRectificationTypeFk,
options: cplusRectificationTypes.value,
model: 'cplusRectificationTypeFk',
@ -43,10 +41,10 @@ const columns = computed(() => [
},
{
name: 'class',
label: useCapitalize(t('globals.class')),
field: (row) => row.siiTypeInvoiceOutFk,
options: siiTypeInvoiceOuts.value,
model: 'siiTypeInvoiceOutFk',
label: capitalize(t('globals.class')),
field: (row) => row.siiTypeInvoiceInFk,
options: siiTypeInvoiceIns.value,
model: 'siiTypeInvoiceInFk',
optionValue: 'id',
optionLabel: 'code',
sortable: true,
@ -55,7 +53,7 @@ const columns = computed(() => [
},
{
name: 'reason',
label: useCapitalize(t('globals.reason')),
label: capitalize(t('globals.reason')),
field: (row) => row.invoiceCorrectionTypeFk,
options: invoiceCorrectionTypes.value,
model: 'invoiceCorrectionTypeFk',
@ -67,13 +65,10 @@ const columns = computed(() => [
},
]);
const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]);
const siiTypeInvoiceIns = ref([]);
const invoiceCorrectionTypes = ref([]);
const rowsSelected = ref([]);
const requiredFieldRule = (val) => val || t('globals.requiredField');
const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`);
</script>
<template>
<FetchData
@ -82,9 +77,9 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
auto-load
/>
<FetchData
url="SiiTypeInvoiceOuts"
url="SiiTypeInvoiceIns"
:where="{ code: { like: 'R%' } }"
@on-fetch="(data) => (siiTypeInvoiceOuts = data)"
@on-fetch="(data) => (siiTypeInvoiceIns = data)"
auto-load
/>
<FetchData
@ -99,17 +94,14 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
url="InvoiceInCorrections"
:filter="filter"
auto-load
v-model:selected="rowsSelected"
primary-key="correctingFk"
@save-changes="onSave"
:default-remove="false"
>
<template #body="{ rows }">
<QTable
v-model:selected="rowsSelected"
:columns="columns"
:rows="rows"
row-key="$index"
selection="single"
:grid="$q.screen.lt.sm"
:pagination="{ rowsPerPage: 0 }"
>
@ -121,8 +113,17 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:readonly="row.invoiceIn.isBooked"
/>
:disable="row.invoiceIn.isBooked"
:filter-options="['description']"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.description }}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-class="{ row, col }">
@ -134,8 +135,20 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
:option-value="col.optionValue"
:option-label="col.optionLabel"
:rules="[requiredFieldRule]"
:readonly="row.invoiceIn.isBooked"
/>
:filter-options="['code', 'description']"
:disable="row.invoiceIn.isBooked"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel
>{{ opt.code }} -
{{ opt.description }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-reason="{ row, col }">
@ -147,7 +160,7 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
:option-value="col.optionValue"
:option-label="col.optionLabel"
:rules="[requiredFieldRule]"
:readonly="row.invoiceIn.isBooked"
:disable="row.invoiceIn.isBooked"
/>
</QTd>
</template>
@ -155,7 +168,6 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`
</template>
</CrudModel>
</template>
<style lang="scss" scoped></style>
<i18n>
es:
Original invoice: Factura origen

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, reactive, computed, onBeforeMount } from 'vue';
import { ref, reactive, computed, onBeforeMount, capitalize } from 'vue';
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
@ -15,7 +15,6 @@ import FetchData from 'src/components/FetchData.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useCapitalize } from 'src/composables/useCapitalize';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceInToBook from '../InvoiceInToBook.vue';
@ -37,7 +36,7 @@ const totalAmount = ref();
const currentAction = ref();
const config = ref();
const cplusRectificationTypes = ref([]);
const siiTypeInvoiceOuts = ref([]);
const siiTypeInvoiceIns = ref([]);
const invoiceCorrectionTypes = ref([]);
const actions = {
unbook: {
@ -91,7 +90,7 @@ const routes = reactive({
return {
name: 'InvoiceInList',
query: {
params: JSON.stringify({ supplierFk: id }),
table: JSON.stringify({ supplierFk: id }),
},
};
},
@ -100,7 +99,7 @@ const routes = reactive({
return {
name: 'InvoiceInList',
query: {
params: JSON.stringify({ correctedFk: entityId.value }),
table: JSON.stringify({ correctedFk: entityId.value }),
},
};
}
@ -119,21 +118,21 @@ const routes = reactive({
const correctionFormData = reactive({
invoiceReason: 2,
invoiceType: 2,
invoiceClass: 6,
invoiceClass: 8,
});
const isNotFilled = computed(() => Object.values(correctionFormData).includes(null));
onBeforeMount(async () => {
await setInvoiceCorrection(entityId.value);
const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`);
totalAmount.value = data.totalDueDay;
totalAmount.value = data.totalTaxableBase;
});
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
await setInvoiceCorrection(to.params.id);
const { data } = await axios.get(`InvoiceIns/${to.params.id}/getTotals`);
totalAmount.value = data.totalDueDay;
totalAmount.value = data.totalTaxableBase;
}
});
@ -207,7 +206,8 @@ const isAgricultural = () => {
};
function showPdfInvoice() {
if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`);
if (isAgricultural())
openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`, null, '_blank');
}
function sendPdfInvoiceConfirmation() {
@ -262,9 +262,9 @@ const createInvoiceInCorrection = async () => {
auto-load
/>
<FetchData
url="SiiTypeInvoiceOuts"
url="siiTypeInvoiceIns"
:where="{ code: { like: 'R%' } }"
@on-fetch="(data) => (siiTypeInvoiceOuts = data)"
@on-fetch="(data) => (siiTypeInvoiceIns = data)"
auto-load
/>
<FetchData
@ -355,10 +355,13 @@ const createInvoiceInCorrection = async () => {
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('invoiceIn.list.issued')" :value="toDate(entity.issued)" />
<VnLv :label="t('invoiceIn.summary.booked')" :value="toDate(entity.booked)" />
<VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" />
<VnLv :label="t('invoiceIn.list.supplier')">
<VnLv :label="t('InvoiceIn.list.issued')" :value="toDate(entity.issued)" />
<VnLv
:label="t('InvoiceIn.summary.bookedDate')"
:value="toDate(entity.booked)"
/>
<VnLv :label="t('InvoiceIn.list.amount')" :value="toCurrency(totalAmount)" />
<VnLv :label="t('InvoiceIn.list.supplier')">
<template #value>
<span class="link">
{{ entity?.supplier?.nickname }}
@ -375,7 +378,7 @@ const createInvoiceInCorrection = async () => {
color="primary"
:to="routes.getSupplier(entity.supplierFk)"
>
<QTooltip>{{ t('invoiceIn.list.supplier') }}</QTooltip>
<QTooltip>{{ t('InvoiceIn.list.supplier') }}</QTooltip>
</QBtn>
<QBtn
size="md"
@ -391,7 +394,7 @@ const createInvoiceInCorrection = async () => {
color="primary"
:to="routes.getTickets(entity.supplierFk)"
>
<QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip>
<QTooltip>{{ t('InvoiceIn.descriptor.ticketList') }}</QTooltip>
</QBtn>
<QBtn
v-if="
@ -435,9 +438,9 @@ const createInvoiceInCorrection = async () => {
readonly
/>
<VnSelect
:label="`${useCapitalize(t('globals.class'))}`"
:label="`${capitalize(t('globals.class'))}`"
v-model="correctionFormData.invoiceClass"
:options="siiTypeInvoiceOuts"
:options="siiTypeInvoiceIns"
option-value="id"
option-label="code"
:required="true"
@ -445,15 +448,27 @@ const createInvoiceInCorrection = async () => {
</QItemSection>
<QItemSection>
<VnSelect
:label="`${useCapitalize(t('globals.type'))}`"
:label="`${capitalize(t('globals.type'))}`"
v-model="correctionFormData.invoiceType"
:options="cplusRectificationTypes"
option-value="id"
option-label="description"
:required="true"
/>
>
<template #option="{ opt }">
<QItem>
<QItemSection>
<QItemLabel
>{{ opt.code }} -
{{ opt.description }}</QItemLabel
>
</QItemSection>
</QItem>
<div></div>
</template>
</VnSelect>
<VnSelect
:label="`${useCapitalize(t('globals.reason'))}`"
:label="`${capitalize(t('globals.reason'))}`"
v-model="correctionFormData.invoiceReason"
:options="invoiceCorrectionTypes"
option-value="id"

View File

@ -25,6 +25,7 @@ const banks = ref([]);
const invoiceInFormRef = ref();
const invoiceId = +route.params.id;
const filter = { where: { invoiceInFk: invoiceId } };
const areRows = ref(false);
const columns = computed(() => [
{
@ -143,8 +144,6 @@ async function insert() {
}"
:disable="!isNotEuro(currency)"
v-model="row.foreignValue"
clearable
clear-icon="close"
/>
</QTd>
</template>
@ -230,7 +229,14 @@ async function insert() {
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn color="primary" icon="add" shortcut="+" size="lg" round @click="insert" />
<QBtn
color="primary"
icon="add"
shortcut="+"
size="lg"
round
@click="!areRows ? insert() : invoiceInFormRef.insert()"
/>
</QPageSticky>
</template>
<style lang="scss" scoped>

View File

@ -26,7 +26,7 @@ const columns = computed(() => [
options: intrastats.value,
model: 'intrastatFk',
optionValue: 'id',
optionLabel: 'description',
optionLabel: (row) => `${row.id}: ${row.description}`,
sortable: true,
tabIndex: 1,
align: 'left',
@ -68,12 +68,6 @@ const columns = computed(() => [
align: 'left',
},
]);
const formatOpt = (row, { model, options }, prop) => {
const obj = row[model];
const option = options.find(({ id }) => id == obj);
return option ? `${obj}:${option[prop]}` : '';
};
</script>
<template>
<FetchData
@ -118,12 +112,10 @@ const formatOpt = (row, { model, options }, prop) => {
<VnSelect
v-model="row[col.model]"
:options="col.options"
option-value="id"
option-label="description"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'description']"
:hide-selected="false"
:fill-input="false"
:display-value="formatOpt(row, col, 'description')"
data-cy="intrastat-code"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -138,8 +130,8 @@ const formatOpt = (row, { model, options }, prop) => {
<VnSelect
v-model="row[col.model]"
:options="col.options"
option-value="id"
option-label="code"
:option-value="col.optionValue"
:option-label="col.optionLabel"
/>
</QTd>
</template>
@ -154,7 +146,7 @@ const formatOpt = (row, { model, options }, prop) => {
{{ getTotal(rows, 'net') }}
</QTd>
<QTd>
{{ getTotal(rows, 'stems') }}
{{ getTotal(rows, 'stems', { decimalPlaces: 0 }) }}
</QTd>
<QTd />
</QTr>
@ -169,12 +161,14 @@ const formatOpt = (row, { model, options }, prop) => {
<QList>
<QItem>
<VnSelect
:label="t('code')"
:label="t('Code')"
class="full-width"
v-model="props.row['intrastatFk']"
:options="intrastats"
option-value="id"
option-label="description"
:option-label="
(row) => `${row.id}:${row.description}`
"
:filter-options="['id', 'description']"
>
<template #option="scope">
@ -248,11 +242,6 @@ const formatOpt = (row, { model, options }, prop) => {
}
}
</style>
<style lang="scss" scoped>
:deep(.q-table tr .q-td:nth-child(2) input) {
display: none;
}
</style>
<i18n>
en:
amount: Amount
@ -261,7 +250,7 @@ const formatOpt = (row, { model, options }, prop) => {
country: Country
es:
Code: Código
amount: Cantidad
amount: Valor mercancía
net: Neto
stems: Tallos
country: País

View File

@ -26,14 +26,14 @@ const intrastatTotals = ref({ amount: 0, net: 0, stems: 0 });
const vatColumns = ref([
{
name: 'expense',
label: 'invoiceIn.summary.expense',
label: 'InvoiceIn.summary.expense',
field: (row) => row.expenseFk,
sortable: true,
align: 'left',
},
{
name: 'landed',
label: 'invoiceIn.summary.taxableBase',
label: 'InvoiceIn.summary.taxableBase',
field: (row) => row.taxableBase,
format: (value) => toCurrency(value),
sortable: true,
@ -41,7 +41,7 @@ const vatColumns = ref([
},
{
name: 'vat',
label: 'invoiceIn.summary.sageVat',
label: 'InvoiceIn.summary.sageVat',
field: (row) => {
if (row.taxTypeSage) return `#${row.taxTypeSage.id} : ${row.taxTypeSage.vat}`;
},
@ -51,7 +51,7 @@ const vatColumns = ref([
},
{
name: 'transaction',
label: 'invoiceIn.summary.sageTransaction',
label: 'InvoiceIn.summary.sageTransaction',
field: (row) => {
if (row.transactionTypeSage)
return `#${row.transactionTypeSage.id} : ${row.transactionTypeSage?.transaction}`;
@ -62,7 +62,7 @@ const vatColumns = ref([
},
{
name: 'rate',
label: 'invoiceIn.summary.rate',
label: 'InvoiceIn.summary.rate',
field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate),
format: (value) => toCurrency(value),
sortable: true,
@ -70,7 +70,7 @@ const vatColumns = ref([
},
{
name: 'currency',
label: 'invoiceIn.summary.currency',
label: 'InvoiceIn.summary.currency',
field: (row) => row.foreignValue,
format: (val) => val && toCurrency(val, currency.value),
sortable: true,
@ -81,21 +81,21 @@ const vatColumns = ref([
const dueDayColumns = ref([
{
name: 'date',
label: 'invoiceIn.summary.dueDay',
label: 'InvoiceIn.summary.dueDay',
field: (row) => toDate(row.dueDated),
sortable: true,
align: 'left',
},
{
name: 'bank',
label: 'invoiceIn.summary.bank',
label: 'InvoiceIn.summary.bank',
field: (row) => row.bank.bank,
sortable: true,
align: 'left',
},
{
name: 'amount',
label: 'invoiceIn.list.amount',
label: 'InvoiceIn.list.amount',
field: (row) => row.amount,
format: (value) => toCurrency(value),
sortable: true,
@ -103,7 +103,7 @@ const dueDayColumns = ref([
},
{
name: 'landed',
label: 'invoiceIn.summary.foreignValue',
label: 'InvoiceIn.summary.foreignValue',
field: (row) => row.foreignValue,
format: (val) => val && toCurrency(val, currency.value),
sortable: true,
@ -114,7 +114,7 @@ const dueDayColumns = ref([
const intrastatColumns = ref([
{
name: 'code',
label: 'invoiceIn.summary.code',
label: 'InvoiceIn.summary.code',
field: (row) => {
return `${row.intrastat.id}: ${row.intrastat?.description}`;
},
@ -123,21 +123,21 @@ const intrastatColumns = ref([
},
{
name: 'amount',
label: 'invoiceIn.list.amount',
label: 'InvoiceIn.list.amount',
field: (row) => toCurrency(row.amount),
sortable: true,
align: 'left',
},
{
name: 'net',
label: 'invoiceIn.summary.net',
label: 'InvoiceIn.summary.net',
field: (row) => row.net,
sortable: true,
align: 'left',
},
{
name: 'stems',
label: 'invoiceIn.summary.stems',
label: 'InvoiceIn.summary.stems',
field: (row) => row.stems,
format: (value) => value,
sortable: true,
@ -145,7 +145,7 @@ const intrastatColumns = ref([
},
{
name: 'landed',
label: 'invoiceIn.summary.country',
label: 'InvoiceIn.summary.country',
field: (row) => row.country?.code,
format: (value) => value,
sortable: true,
@ -210,7 +210,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
/>
</QCardSection>
<VnLv
:label="t('invoiceIn.list.supplier')"
:label="t('InvoiceIn.list.supplier')"
:value="entity.supplier?.name"
>
<template #value>
@ -221,14 +221,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</template>
</VnLv>
<VnLv
:label="t('invoiceIn.list.supplierRef')"
:label="t('InvoiceIn.list.supplierRef')"
:value="entity.supplierRef"
/>
<VnLv
:label="t('invoiceIn.summary.currency')"
:label="t('InvoiceIn.summary.currency')"
:value="entity.currency?.code"
/>
<VnLv :label="t('invoiceIn.serial')" :value="`${entity.serial}`" />
<VnLv :label="t('InvoiceIn.serial')" :value="`${entity.serial}`" />
<VnLv
:label="t('globals.country')"
:value="entity.supplier?.country?.code"
/>
</QCard>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
@ -239,21 +243,22 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCardSection>
<VnLv
:ellipsis-value="false"
:label="t('invoiceIn.summary.issued')"
:label="t('InvoiceIn.summary.issued')"
:value="toDate(entity.issued)"
/>
<VnLv
:label="t('invoiceIn.summary.operated')"
:label="t('InvoiceIn.summary.operated')"
:value="toDate(entity.operated)"
/>
<VnLv
:label="t('invoiceIn.summary.bookEntried')"
:label="t('InvoiceIn.summary.bookEntried')"
:value="toDate(entity.bookEntried)"
/>
<VnLv
:label="t('invoiceIn.summary.bookedDate')"
:label="t('InvoiceIn.summary.bookedDate')"
:value="toDate(entity.booked)"
/>
<VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" />
</QCard>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
@ -263,18 +268,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
/>
</QCardSection>
<VnLv
:label="t('invoiceIn.summary.sage')"
:label="t('InvoiceIn.summary.sage')"
:value="entity.sageWithholding?.withholding"
/>
<VnLv
:label="t('invoiceIn.summary.vat')"
:label="t('InvoiceIn.summary.vat')"
:value="entity.expenseDeductible?.name"
/>
<VnLv
:label="t('invoiceIn.card.company')"
:label="t('InvoiceIn.card.company')"
:value="entity.company?.code"
/>
<VnLv :label="t('invoiceIn.isBooked')" :value="invoiceIn?.isBooked" />
<VnLv :label="t('InvoiceIn.isBooked')" :value="invoiceIn?.isBooked" />
</QCard>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
@ -285,11 +290,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCardSection>
<QCardSection class="q-pa-none">
<VnLv
:label="t('invoiceIn.summary.taxableBase')"
:label="t('InvoiceIn.summary.taxableBase')"
:value="toCurrency(entity.totals.totalTaxableBase)"
/>
<VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" />
<VnLv :label="t('invoiceIn.summary.dueTotal')">
<VnLv :label="t('InvoiceIn.summary.dueTotal')">
<template #value>
<QChip
dense
@ -297,8 +302,8 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
:color="amountsNotMatch ? 'negative' : 'transparent'"
:title="
amountsNotMatch
? t('invoiceIn.summary.noMatch')
: t('invoiceIn.summary.dueTotal')
? t('InvoiceIn.summary.noMatch')
: t('InvoiceIn.summary.dueTotal')
"
>
{{ toCurrency(entity.totals.totalDueDay) }}
@ -309,7 +314,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCard>
<!--Vat-->
<QCard v-if="entity.invoiceInTax.length" class="vat">
<VnTitle :url="getLink('vat')" :text="t('invoiceIn.card.vat')" />
<VnTitle :url="getLink('vat')" :text="t('InvoiceIn.card.vat')" />
<QTable
:columns="vatColumns"
:rows="entity.invoiceInTax"
@ -357,7 +362,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCard>
<!--Due Day-->
<QCard v-if="entity.invoiceInDueDay.length" class="due-day">
<VnTitle :url="getLink('due-day')" :text="t('invoiceIn.card.dueDay')" />
<VnTitle :url="getLink('due-day')" :text="t('InvoiceIn.card.dueDay')" />
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
<template #header="dueDayProps">
<QTr :props="dueDayProps" class="bg">
@ -395,7 +400,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCard v-if="entity.invoiceInIntrastat.length">
<VnTitle
:url="getLink('intrastat')"
:text="t('invoiceIn.card.intrastat')"
:text="t('InvoiceIn.card.intrastat')"
/>
<QTable
:columns="intrastatColumns"

View File

@ -11,12 +11,14 @@ import CrudModel from 'src/components/CrudModel.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue';
import { getExchange } from 'src/composables/getExchange';
import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard';
const { t } = useI18n();
const arrayData = useArrayData();
const route = useRoute();
const invoiceIn = computed(() => arrayData.store.data);
const invoiceId = +useRoute().params.id;
const currency = computed(() => invoiceIn.value?.currency?.code);
const expenses = ref([]);
const sageTaxTypes = ref([]);
@ -39,9 +41,8 @@ const columns = computed(() => [
options: expenses.value,
model: 'expenseFk',
optionValue: 'id',
optionLabel: 'id',
optionLabel: (row) => `${row.id}: ${row.name}`,
sortable: true,
tabIndex: 1,
align: 'left',
},
{
@ -50,7 +51,6 @@ const columns = computed(() => [
field: (row) => row.taxableBase,
model: 'taxableBase',
sortable: true,
tabIndex: 2,
align: 'left',
},
{
@ -60,9 +60,8 @@ const columns = computed(() => [
options: sageTaxTypes.value,
model: 'taxTypeSageFk',
optionValue: 'id',
optionLabel: 'id',
optionLabel: (row) => `${row.id}: ${row.vat}`,
sortable: true,
tabindex: 3,
align: 'left',
},
{
@ -72,16 +71,14 @@ const columns = computed(() => [
options: sageTransactionTypes.value,
model: 'transactionTypeSageFk',
optionValue: 'id',
optionLabel: 'id',
optionLabel: (row) => `${row.id}: ${row.transaction}`,
sortable: true,
tabIndex: 4,
align: 'left',
},
{
name: 'rate',
label: t('Rate'),
sortable: true,
tabIndex: 5,
field: (row) => taxRate(row, row.taxTypeSageFk),
align: 'left',
},
@ -89,7 +86,6 @@ const columns = computed(() => [
name: 'foreignvalue',
label: t('Foreign value'),
sortable: true,
tabIndex: 6,
field: (row) => row.foreignValue,
align: 'left',
},
@ -106,7 +102,7 @@ const filter = {
'transactionTypeSageFk',
],
where: {
invoiceInFk: invoiceId,
invoiceInFk: route.params.id,
},
};
@ -120,14 +116,20 @@ function taxRate(invoiceInTax) {
const taxTypeSage = taxRateSelection?.rate ?? 0;
const taxableBase = invoiceInTax?.taxableBase ?? 0;
return (taxTypeSage / 100) * taxableBase;
return ((taxTypeSage / 100) * taxableBase).toFixed(2);
}
const formatOpt = (row, { model, options }, prop) => {
const obj = row[model];
const option = options.find(({ id }) => id == obj);
return option ? `${obj}:${option[prop]}` : '';
};
function autocompleteExpense(evt, row, col) {
const val = evt.target.value;
if (!val) return;
const param = isNaN(val) ? row[col.model] : val;
const lookup = expenses.value.find(
({ id }) => id == useAccountShortToStandard(param)
);
if (lookup) row[col.model] = lookup;
}
</script>
<template>
<FetchData
@ -148,10 +150,10 @@ const formatOpt = (row, { model, options }, prop) => {
data-key="InvoiceInTaxes"
url="InvoiceInTaxes"
:filter="filter"
:data-required="{ invoiceInFk: invoiceId }"
:data-required="{ invoiceInFk: $route.params.id }"
auto-load
v-model:selected="rowsSelected"
:go-to="`/invoice-in/${invoiceId}/due-day`"
:go-to="`/invoice-in/${$route.params.id}/due-day`"
>
<template #body="{ rows }">
<QTable
@ -171,6 +173,7 @@ const formatOpt = (row, { model, options }, prop) => {
:option-label="col.optionLabel"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
@keydown.tab="autocompleteExpense($event, row, col)"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -187,13 +190,7 @@ const formatOpt = (row, { model, options }, prop) => {
</template>
<template #body-cell-taxablebase="{ row }">
<QTd>
{{ currency }}
<VnInputNumber
:class="{
'no-pointer-events': isNotEuro(currency),
}"
:disable="isNotEuro(currency)"
label=""
clear-icon="close"
v-model="row.taxableBase"
clearable
@ -208,9 +205,7 @@ const formatOpt = (row, { model, options }, prop) => {
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'vat']"
:hide-selected="false"
:fill-input="false"
:display-value="formatOpt(row, col, 'vat')"
data-cy="vat-sageiva"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -233,11 +228,6 @@ const formatOpt = (row, { model, options }, prop) => {
:option-value="col.optionValue"
:option-label="col.optionLabel"
:filter-options="['id', 'transaction']"
:autofocus="col.tabIndex == 1"
input-debounce="0"
:hide-selected="false"
:fill-input="false"
:display-value="formatOpt(row, col, 'transaction')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -262,6 +252,16 @@ const formatOpt = (row, { model, options }, prop) => {
}"
:disable="!isNotEuro(currency)"
v-model="row.foreignValue"
@update:model-value="
async (val) => {
if (!isNotEuro(currency)) return;
row.taxableBase = await getExchange(
val,
row.currencyFk,
invoiceIn.issued
);
}
"
/>
</QTd>
</template>
@ -305,7 +305,7 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['expenseFk']"
:options="expenses"
option-value="id"
option-label="name"
:option-label="(row) => `${row.id}:${row.name}`"
:filter-options="['id', 'name']"
:tooltip="t('Create a new expense')"
>
@ -339,7 +339,7 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['taxTypeSageFk']"
:options="sageTaxTypes"
option-value="id"
option-label="vat"
:option-label="(row) => `${row.id}:${row.vat}`"
:filter-options="['id', 'vat']"
>
<template #option="scope">
@ -362,7 +362,9 @@ const formatOpt = (row, { model, options }, prop) => {
v-model="props.row['transactionTypeSageFk']"
:options="sageTransactionTypes"
option-value="id"
option-label="transaction"
:option-label="
(row) => `${row.id}:${row.transaction}`
"
:filter-options="['id', 'transaction']"
>
<template #option="scope">
@ -418,11 +420,6 @@ const formatOpt = (row, { model, options }, prop) => {
.bg {
background-color: var(--vn-light-gray);
}
:deep(.q-table tr td:nth-child(n + 4):nth-child(-n + 5) input) {
display: none;
}
@media (max-width: $breakpoint-xs) {
.q-dialog {
.q-card {

View File

@ -83,7 +83,7 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
</template>
</VnSelect>
<VnInput
:label="t('invoiceIn.list.supplierRef')"
:label="t('InvoiceIn.list.supplierRef')"
v-model="data.supplierRef"
/>
</VnRow>
@ -97,10 +97,10 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
map-options
hide-selected
:required="true"
:rules="validate('invoiceIn.companyFk')"
:rules="validate('InvoiceIn.companyFk')"
/>
<VnInputDate
:label="t('invoiceIn.summary.issued')"
:label="t('InvoiceIn.summary.issued')"
v-model="data.issued"
/>
</VnRow>

View File

@ -1,41 +1,66 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSelect from 'components/common/VnSelect.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import FetchData from 'components/FetchData.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { dateRange } from 'src/filters';
import { date } from 'quasar';
const { t } = useI18n();
defineProps({ dataKey: { type: String, required: true } });
const activities = ref([]);
const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
function handleDaysAgo(params, daysAgo) {
const [from, to] = dateRange(Date.vnNew());
if (!daysAgo && daysAgo !== 0) {
Object.assign(params, { from: undefined, to: undefined });
} else {
from.setDate(from.getDate() - daysAgo);
Object.assign(params, {
from: date.formatDate(from, dateFormat),
to: date.formatDate(to, dateFormat),
});
}
}
</script>
<template>
<FetchData
url="SupplierActivities"
auto-load
@on-fetch="(data) => (activities = data)"
/>
<VnFilterPanel :data-key="dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<VnFilterPanel :data-key="dataKey" :search-button="true" :hidden-tags="['daysAgo']">
<template #tags="{ tag, formatFn, getLocale }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<strong>{{ getLocale(tag.label) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<template #body="{ params, searchFn, getLocale }">
<QItem>
<QItemSection>
<VnInputDate :label="t('From')" v-model="params.from" is-outlined />
<VnInputDate
:label="$t('globals.from')"
v-model="params.from"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate :label="t('To')" v-model="params.to" is-outlined />
<VnInputDate
:label="$t('globals.to')"
v-model="params.to"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputNumber
:label="$t('globals.daysAgo')"
v-model="params.daysAgo"
is-outlined
:step="0"
@update:model-value="(val) => handleDaysAgo(params, val)"
@remove="(val) => handleDaysAgo(params, val)"
/>
</QItemSection>
</QItem>
<QItem>
@ -44,20 +69,18 @@ const activities = ref([]);
v-model="params.supplierFk"
url="Suppliers"
:fields="['id', 'nickname']"
:label="t('params.supplierFk')"
option-value="id"
:label="getLocale('supplierFk')"
option-label="nickname"
dense
outlined
rounded
:filter-options="['id', 'name']"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('params.supplierRef')"
:label="getLocale('supplierRef')"
v-model="params.supplierRef"
is-outlined
lazy-rules
@ -67,7 +90,7 @@ const activities = ref([]);
<QItem>
<QItemSection>
<VnInput
:label="t('params.fi')"
:label="getLocale('fi')"
v-model="params.fi"
is-outlined
lazy-rules
@ -77,7 +100,7 @@ const activities = ref([]);
<QItem>
<QItemSection>
<VnInput
:label="t('params.serial')"
:label="getLocale('serial')"
v-model="params.serial"
is-outlined
lazy-rules
@ -87,7 +110,7 @@ const activities = ref([]);
<QItem>
<QItemSection>
<VnInput
:label="t('params.account')"
:label="getLocale('account')"
v-model="params.account"
is-outlined
lazy-rules
@ -97,7 +120,7 @@ const activities = ref([]);
<QItem>
<QItemSection>
<VnInput
:label="t('params.awb')"
:label="getLocale('globals.params.awbCode')"
v-model="params.awbCode"
is-outlined
lazy-rules
@ -107,24 +130,34 @@ const activities = ref([]);
<QItem>
<QItemSection>
<VnInputNumber
:label="t('Amount')"
:label="$t('globals.amount')"
v-model="params.amount"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.companyFk"
:label="$t('globals.company')"
url="Companies"
option-label="code"
:fields="['id', 'code']"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('invoiceIn.isBooked')"
:label="$t('InvoiceIn.isBooked')"
v-model="params.isBooked"
@update:model-value="searchFn()"
toggle-indeterminate
/>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('params.correctingFk')"
:label="getLocale('params.correctingFk')"
v-model="params.correctingFk"
@update:model-value="searchFn()"
toggle-indeterminate
@ -134,54 +167,3 @@ const activities = ref([]);
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
search: Id or supplier name
supplierRef: Supplier ref.
supplierFk: Supplier
fi: Supplier fiscal id
clientFk: Customer
amount: Amount
created: Created
awb: AWB
dued: Dued
serialNumber: Serial Number
serial: Serial
account: Ledger account
isBooked: is booked
correctedFk: Rectified
issued: Issued
to: To
from: From
awbCode: AWB
correctingFk: Rectificative
supplierActivityFk: Supplier activity
es:
params:
search: Id o nombre proveedor
supplierRef: Ref. proveedor
supplierFk: Proveedor
clientFk: Cliente
fi: CIF proveedor
serialNumber: Num. serie
serial: Serie
awb: AWB
amount: Importe
issued: Emitida
isBooked: Contabilizada
account: Cuenta contable
created: Creada
dued: Vencida
correctedFk: Rectificada
correctingFk: Rectificativa
supplierActivityFk: Actividad proveedor
from: Desde
to: Hasta
From: Desde
To: Hasta
Amount: Importe
Issued: Fecha factura
Id or supplier: Id o proveedor
</i18n>

View File

@ -2,6 +2,7 @@
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import { downloadFile } from 'src/composables/downloadFile';
import { toDate, toCurrency } from 'src/filters/index';
import InvoiceInFilter from './InvoiceInFilter.vue';
@ -14,8 +15,10 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import FetchData from 'src/components/FetchData.vue';
const stateStore = useStateStore();
const user = useState().getUser();
const { viewSummary } = useSummaryDialog();
const { t } = useI18n();
@ -23,7 +26,14 @@ onMounted(async () => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const tableRef = ref();
const companies = ref([]);
const cols = computed(() => [
{
align: 'left',
name: 'isBooked',
label: t('InvoiceIn.isBooked'),
columnFilter: false,
},
{
align: 'left',
name: 'id',
@ -32,7 +42,7 @@ const cols = computed(() => [
{
align: 'left',
name: 'supplierFk',
label: t('invoiceIn.list.supplier'),
label: t('InvoiceIn.list.supplier'),
columnFilter: {
component: 'select',
attrs: {
@ -45,16 +55,16 @@ const cols = computed(() => [
{
align: 'left',
name: 'supplierRef',
label: t('invoiceIn.list.supplierRef'),
label: t('InvoiceIn.list.supplierRef'),
},
{
align: 'left',
name: 'serial',
label: t('invoiceIn.serial'),
label: t('InvoiceIn.serial'),
},
{
align: 'left',
label: t('invoiceIn.list.issued'),
label: t('InvoiceIn.list.issued'),
name: 'issued',
component: null,
columnFilter: {
@ -64,21 +74,38 @@ const cols = computed(() => [
},
{
align: 'left',
name: 'isBooked',
label: t('invoiceIn.isBooked'),
columnFilter: false,
label: t('InvoiceIn.list.dueDated'),
name: 'dueDated',
component: null,
columnFilter: {
component: 'date',
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.dueDated)),
},
{
align: 'left',
name: 'awbCode',
label: t('invoiceIn.list.awb'),
label: t('InvoiceIn.list.awb'),
},
{
align: 'left',
name: 'amount',
label: t('invoiceIn.list.amount'),
label: t('InvoiceIn.list.amount'),
format: ({ amount }) => toCurrency(amount),
},
{
name: 'companyFk',
label: t('globals.company'),
columnFilter: {
component: 'select',
attrs: {
url: 'Companies',
fields: ['id', 'code'],
optionLabel: 'code',
},
},
format: (row) => row.code,
},
{
align: 'right',
name: 'tableActions',
@ -87,6 +114,7 @@ const cols = computed(() => [
title: t('components.smartCard.openSummary'),
icon: 'preview',
type: 'submit',
isPrimary: true,
action: (row) => viewSummary(row.id, InvoiceInSummary),
},
{
@ -101,6 +129,7 @@ const cols = computed(() => [
]);
</script>
<template>
<FetchData url="Companies" @on-fetch="(data) => (companies = data)" auto-load />
<InvoiceInSearchbar />
<RightMenu>
<template #right-panel>
@ -116,7 +145,7 @@ const cols = computed(() => [
urlCreate: 'InvoiceIns',
title: t('globals.createInvoiceIn'),
onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {},
formInitialData: { companyFk: user.companyFk, issued: Date.vnNew() },
}"
redirect="invoice-in"
:columns="cols"
@ -151,7 +180,7 @@ const cols = computed(() => [
</template>
</VnSelect>
<VnInput
:label="t('invoiceIn.list.supplierRef')"
:label="t('InvoiceIn.list.supplierRef')"
v-model="data.supplierRef"
/>
<VnSelect
@ -163,7 +192,7 @@ const cols = computed(() => [
option-label="code"
:required="true"
/>
<VnInputDate :label="t('invoiceIn.summary.issued')" v-model="data.issued" />
<VnInputDate :label="t('InvoiceIn.summary.issued')" v-model="data.issued" />
</template>
</VnTable>
</template>

View File

@ -58,6 +58,14 @@ onBeforeMount(async () => {
:right-search="false"
:user-params="{ daysAgo }"
:disable-option="{ card: true }"
:row-click="
(row) => {
$router.push({
name: 'InvoiceInList',
query: { table: JSON.stringify({ serial: row.serial }) },
});
}
"
auto-load
/>
</template>

View File

@ -8,7 +8,11 @@ defineProps({ dataKey: { type: String, required: true } });
const { t } = useI18n();
</script>
<template>
<VnFilterPanel :data-key="dataKey" :search-button="true">
<VnFilterPanel
:data-key="dataKey"
:search-button="true"
:unremovable-params="['daysAgo']"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>

View File

@ -1,4 +1,4 @@
invoiceIn:
InvoiceIn:
serial: Serial
isBooked: Is booked
list:
@ -7,8 +7,11 @@ invoiceIn:
supplierRef: Supplier ref.
file: File
issued: Issued
dueDated: Due dated
awb: AWB
amount: Amount
descriptor:
ticketList: Ticket list
card:
client: Client
company: Company
@ -39,3 +42,9 @@ invoiceIn:
net: Net
stems: Stems
country: Country
params:
search: Id or supplier name
account: Ledger account
correctingFk: Rectificative
correctedFk: Corrected
isBooked: Is booked

View File

@ -1,15 +1,17 @@
invoiceIn:
InvoiceIn:
serial: Serie
isBooked: Contabilizada
list:
ref: Referencia
supplier: Proveedor
supplierRef: Ref. proveedor
shortIssued: F. emisión
issued: F. emisión
dueDated: F. vencimiento
file: Fichero
issued: Fecha emisión
awb: AWB
amount: Importe
descriptor:
ticketList: Listado de tickets
card:
client: Cliente
company: Empresa
@ -38,3 +40,8 @@ invoiceIn:
net: Neto
stems: Tallos
country: País
params:
search: Id o nombre proveedor
account: Cuenta contable
correctingFk: Rectificativa
correctedFk: Rectificada

View File

@ -115,6 +115,9 @@ onMounted(async () => {
<VnSelect
:label="t('invoiceOutSerialType')"
v-model="formData.serialType"
@update:model-value="
invoiceOutGlobalStore.fetchInvoiceOutConfig(formData)
"
:options="serialTypesOptions"
option-value="type"
option-label="type"

View File

@ -351,7 +351,7 @@ watchEffect(selectedRows);
<VnSelect
url="InvoiceOutSerials"
v-model="data.serial"
:label="t('invoiceIn.serial')"
:label="t('InvoiceIn.serial')"
:options="invoiceOutSerialsOptions"
option-label="description"
option-value="code"

View File

@ -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>

View File

@ -70,6 +70,7 @@ const onIntrastatCreated = (response, formData) => {
option-label="name"
hide-selected
map-options
required
>
<template #option="scope">
<QItem v-bind="scope.itemProps">

View File

@ -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();
};

View File

@ -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>

View File

@ -1,21 +1,21 @@
<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 SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
const { t } = useI18n();
const route = useRoute();
const stateStore = useStateStore();
const from = ref();
const to = ref();
const exprBuilder = (param, value) => {
switch (param) {
@ -36,25 +36,23 @@ const exprBuilder = (param, value) => {
}
};
const from = ref();
const to = ref();
const where = {
itemFk: route.params.id,
};
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,45 +60,52 @@ 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),
},
{
label: t('shelvings.packing'),
label: 'Packing',
name: 'packing',
field: 'packing',
align: 'center',
},
{
label: t('lastEntries.grouping'),
name: 'grouping',
field: 'grouping',
align: 'center',
},
{
@ -111,18 +116,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 +140,9 @@ const columns = computed(() => [
},
{
label: t('lastEntries.supplier'),
name: 'stems',
name: 'supplier',
field: 'supplier',
align: 'left',
align: 'center',
},
]);
@ -160,7 +166,10 @@ 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;
await fetchItemLastEntries();
};
@ -181,9 +190,10 @@ onMounted(async () => {
});
});
onUnmounted(() => (stateStore.rightDrawer = false));
function getBadgeClass(groupingMode, expectedGrouping) {
return groupingMode === expectedGrouping ? 'accent-badge' : 'simple-badge';
}
</script>
<template>
<VnSubToolbar>
<template #st-data>
@ -192,27 +202,43 @@ 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"
/>
<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-warehouse="{ row }">
<QTd>
<span>{{ row.warehouse }}</span>
</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">
@ -221,32 +247,37 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</div>
</QTd>
</template>
<template #body-cell-pvp="{ value }">
<QTd @click.stop class="text-center">
<span> {{ value }}</span>
<QTooltip> {{ t('lastEntries.grouping') }}/Packing </QTooltip></QTd
>
</template>
<template #body-cell-printedStickers="{ row }">
<QTd @click.stop class="text-center">
<span style="color: var(--vn-label-color)">
{{ row.printedStickers }}</span
>
</QTd>
</template>
<template #body-cell-packing="{ row }">
<QTd @click.stop>
<QBadge
class="center-content"
:class="getBadgeClass(row.groupingMode, 'packing')"
rounded
:color="row.groupingMode == 'packing' ? 'grey-13' : 'black'"
>
{{ dashIfEmpty(row.packing) }}
<QTooltip>{{ t('lastEntries.packing') }}</QTooltip>
<QTooltip>Packing</QTooltip>
</QBadge>
</QTd>
</template>
<template #body-cell-pvp="{ value }">
<QTd @click.stop
><span> {{ value }}</span>
<QTooltip>
{{ t('lastEntries.grouping') }}/{{ t('lastEntries.packing') }}
</QTooltip></QTd
>
</template>
<template #body-cell-grouping="{ row }">
<QTd @click.stop>
<QBadge
class="center-content"
:class="getBadgeClass(row.groupingMode, 'grouping')"
rounded
:color="row.groupingMode == 'grouping' ? 'grey-13' : 'black'"
>
{{ dashIfEmpty(row.grouping) }}
<QTooltip>{{ t('lastEntries.grouping') }}</QTooltip>
@ -254,7 +285,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 +303,28 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</span>
</QTd>
</template>
<template #body-cell-supplier="{ row }">
<QTd @click.stop>
<div class="full-width flex justify-left">
<QBadge
:class="
row.isInventorySupplier ? 'bg-vn-page' : 'transparent'
"
dense
>
<SupplierDescriptorProxy :id="row.supplierFk" />
<span class="link">{{ row.supplier }}</span>
</QBadge>
</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 +336,19 @@ onUnmounted(() => (stateStore.rightDrawer = false));
padding: 0 11px;
height: 28px;
}
.th :first-child {
.td {
text-align: center;
background-color: red;
}
}
.accent-badge {
background-color: var(--vn-label-color);
color: var(--vn-text-color-contrast);
}
.simple-badge {
background-color: transparent;
color: var(--vn-text-color);
font-size: 14px;
}
</style>

View File

@ -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,58 +46,32 @@ 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,
component: 'select',
attrs: {
url: 'parkings',
fields: ['code'],
@ -104,20 +80,13 @@ const columns = computed(() => [
'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,
component: 'select',
attrs: {
url: 'shelvings',
fields: ['code'],
@ -126,41 +95,20 @@ const columns = computed(() => [
'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>

View File

@ -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')"

View File

@ -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,
@ -126,7 +128,7 @@ const insertTag = (rows) => {
<VnSelect
v-if="row.tag?.isFree === false"
:key="row.tagFk"
:label="t('Value')"
:label="t('itemTags.value')"
v-model="row.value"
:url="`Tags/${row.tagFk}/filterValue`"
option-label="value"
@ -147,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')"
@ -154,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
@ -189,3 +193,8 @@ const insertTag = (rows) => {
</QPage>
</div>
</template>
<i18n>
es:
Tags can not be repeated: Las etiquetas no pueden repetirse
</i18n>

View File

@ -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');
};

View File

@ -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 }">
@ -372,6 +368,8 @@ const columns = computed(() => [
<i18n>
es:
New item: Nuevo artículo
Create Item: Crear artículo
You can search by id: Puedes buscar por id
Preview: Vista previa
Regularize stock: Regularizar stock
</i18n>

View File

@ -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>

View File

@ -11,8 +11,10 @@ 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 RightMenu from 'src/components/common/RightMenu.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();
@ -216,7 +218,7 @@ const onDenyAccept = (_, responseData) => {
<template>
<RightMenu>
<template #right-panel>
<ItemRequestFilter data-key="itemRequest" ref="tableRef" />
<ItemRequestFilter data-key="itemRequest" />
</template>
</RightMenu>
<VnTable
@ -301,7 +303,6 @@ const onDenyAccept = (_, responseData) => {
</template>
<template #column-denyOptions="{ row, rowIndex }">
<QTd class="sticky no-padding">
<QIcon
v-if="row.response?.length"
name="insert_drive_file"
@ -324,7 +325,6 @@ const onDenyAccept = (_, responseData) => {
{{ t('Discard') }}
</QTooltip>
</QIcon>
</QTd>
</template>
</VnTable>
<QDialog ref="denyFormRef" transition-show="scale" transition-hide="scale">

View File

@ -1,11 +1,13 @@
<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';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import { useArrayData } from 'src/composables/useArrayData';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const props = defineProps({
@ -20,7 +22,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([]);
@ -41,6 +44,19 @@ const exprBuilder = (param, value) => {
};
}
};
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>
@ -129,33 +145,17 @@ const exprBuilder = (param, value) => {
</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>
@ -201,6 +201,7 @@ en:
to: To
mine: For me
state: State
daysOnward: Days onward
myTeam: My team
dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time
denied: Denied
@ -218,6 +219,7 @@ es:
to: Hasta
mine: Para mi
state: Estado
daysOnward: Días en adelante
myTeam: Mi equipo
dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez
denied: Denegada

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();
@ -112,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>
@ -150,6 +134,7 @@ const getLocale = (label) => {
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
@ -225,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

View File

@ -10,21 +10,24 @@ 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';
import { useStateStore } from 'src/stores/useStateStore';
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 PayMethodOpts = ref([]);
const ItemPackingTypeOpts = ref([]);
const stateStore = useStateStore();
const { viewSummary } = useSummaryDialog();
@ -58,6 +61,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 };
}
@ -144,6 +149,7 @@ const columns = computed(() => [
align: 'left',
format: (row) => row.practicalHour,
columnFilter: false,
dense: true,
},
{
label: t('salesTicketsTable.preparation'),
@ -197,6 +203,7 @@ const columns = computed(() => [
'false-value': 0,
'true-value': 1,
},
component: false,
},
{
label: t('salesTicketsTable.zone'),
@ -213,6 +220,21 @@ const columns = computed(() => [
},
},
},
{
label: t('salesTicketsTable.payMethod'),
name: 'payMethod',
align: 'left',
columnFilter: {
component: 'select',
url: 'PayMethods',
attrs: {
options: PayMethodOpts.value,
optionValue: 'id',
optionLabel: 'name',
dense: true,
},
},
},
{
label: t('salesTicketsTable.total'),
name: 'totalWithVat',
@ -226,6 +248,33 @@ const columns = computed(() => [
},
},
},
{
label: t('salesTicketsTable.department'),
name: 'department',
align: 'left',
columnFilter: {
component: 'select',
attrs: {
options: DepartmentOpts.value,
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',
@ -257,19 +306,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) => {
@ -286,14 +322,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>
@ -325,6 +353,33 @@ 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)"
/>
<FetchData
url="PayMethods"
:filter="{
fields: ['id', 'name'],
order: 'id ASC',
}"
auto-load
@on-fetch="(data) => (PayMethodOpts = data)"
/>
<MonitorTicketSearchbar />
<RightMenu>
<template #right-panel>
@ -389,13 +444,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" />

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,7 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import { useRoute, useRouter } from 'vue-router';
import { onMounted, onUnmounted, ref, computed, watch, provide } from 'vue';
import { onMounted, ref, computed, watch } from 'vue';
import axios from 'axios';
import { useI18n } from 'vue-i18n';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
@ -30,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) {
@ -77,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>
@ -103,16 +88,14 @@ provide('onItemSaved', onItemSaved);
:info="t('You can search items by name or id')"
:search-remove-params="false"
/>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<OrderCatalogFilter
:data-key="dataKey"
:tag-value="tagValue"
:tags="tags"
:initial-catalog-params="catalogParams"
/>
</QScrollArea>
</QDrawer>
</Teleport>
<QPage class="column items-center q-pa-md" data-cy="orderCatalogPage">
<div class="full-width">
<VnPaginate

View File

@ -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) => {

View File

@ -1,12 +1,12 @@
<script setup>
import toCurrency from 'src/filters/toCurrency';
import { inject, ref } from 'vue';
import { computed, 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 = () => {

View File

@ -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"

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