#7936 improve InvoiceIn #1004

Merged
jorgep merged 55 commits from 7936-improveInvoiceIn into dev 2024-12-18 09:22:09 +00:00
54 changed files with 564 additions and 493 deletions
Showing only changes of commit d90750b2d5 - Show all commits

View File

@ -1,6 +1,5 @@
function focusFirstInput(input) {
input.focus();
return;
}
export default {
mounted: function () {

View File

@ -293,6 +293,7 @@ defineExpose({
class="q-pa-md"
:style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel"
:prevent-submit="$attrs['prevent-submit']"
>
<QCard>
<slot

View File

@ -162,7 +162,9 @@ onMounted(() => {
: $props.defaultMode;
stateStore.rightDrawer = quasar.screen.gt.xs;
columnsVisibilitySkipped.value = [
...splittedColumns.value.columns.filter((c) => !c.visible).map((c) => c.name),
...splittedColumns.value.columns
.filter((c) => c.visible === false)
.map((c) => c.name),
...['tableActions'],
];
createForm.value = $props.create;

View File

@ -152,7 +152,7 @@ onMounted(async () => {
<QCheckbox
v-for="col in localColumns"
:key="col.name"
:label="col.label"
:label="col.label ?? col.name"
v-model="col.visible"
/>
</div>

View File

@ -0,0 +1,31 @@
<script setup>
import { toDateFormat } from 'src/filters/date.js';
defineProps({ date: { type: [Date, String], required: true } });
function getBadgeAttrs(date) {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let timeDiff = today - timeTicket;
if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' };
if (timeDiff < 0) return { color: 'success', 'text-color': 'black' };
return { color: 'transparent', 'text-color': 'white' };
}
function formatShippedDate(date) {
if (!date) return '-';
const dateSplit = date.split('T');
const [year, month, day] = dateSplit[0].split('-');
const newDate = new Date(year, month - 1, day);
return toDateFormat(newDate);
}
</script>
<template>
<QBadge v-bind="getBadgeAttrs(date)" class="q-pa-sm" style="font-size: 14px">
{{ formatShippedDate(date) }}
</QBadge>
</template>

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

@ -51,10 +51,6 @@ const props = defineProps({
type: Object,
default: null,
},
staticParams: {
type: Array,
default: () => [],
},
exprBuilder: {
type: Function,
default: null,

View File

@ -264,6 +264,10 @@ input::-webkit-inner-spin-button {
.shrink {
max-width: 75px;
}
.number {
text-align: right;
width: 96px;
}
.expand {
max-width: 400px;
}

View File

@ -129,6 +129,7 @@ globals:
small: Small
medium: Medium
big: Big
email: Email
pageTitles:
logIn: Login
addressEdit: Update address

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
@ -508,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.

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

@ -12,6 +12,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const { t } = useI18n();
const route = useRoute();
@ -144,7 +145,6 @@ function handleLocation(data, location) {
:url="`Addresses/${route.params.addressId}`"
@on-data-saved="onDataSaved()"
auto-load
model="customer"
>
<template #moreActions>
<QBtn
@ -220,31 +220,35 @@ function handleLocation(data, location) {
</div>
</VnRow>
<VnRow>
<div class="col">
<VnSelect
:label="t('Incoterms')"
:options="incoterms"
hide-selected
option-label="name"
option-value="code"
v-model="data.incotermsFk"
/>
</div>
<div class="col">
<VnSelectDialog
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
option-label="fiscalName"
option-value="id"
v-model="data.customsAgentFk"
:tooltip="t('New customs agent')"
>
<template #form>
<CustomerNewCustomsAgent />
</template>
</VnSelectDialog>
</div>
<VnSelect
:label="t('Incoterms')"
:options="incoterms"
hide-selected
option-label="name"
option-value="code"
v-model="data.incotermsFk"
/>
<VnSelectDialog
:label="t('Customs agent')"
:options="customsAgents"
hide-selected
option-label="fiscalName"
option-value="id"
v-model="data.customsAgentFk"
:tooltip="t('New customs agent')"
>
<template #form>
<CustomerNewCustomsAgent />
</template>
</VnSelectDialog>
</VnRow>
<VnRow>
<VnInputNumber
:label="t('Longitude')"
clearable
v-model="data.longitude"
/>
<VnInputNumber :label="t('Latitude')" clearable v-model="data.latitude" />
</VnRow>
<h4 class="q-mb-xs">{{ t('Notes') }}</h4>
<VnRow
@ -322,4 +326,6 @@ es:
Description: Descripción
Add note: Añadir nota
Remove note: Eliminar nota
Longitude: Longitud
Latitude: Latitud
</i18n>

View File

@ -189,6 +189,7 @@ async function getAmountPaid() {
:url-create="urlCreate"
:mapper="onBeforeSave"
@on-data-saved="onDataSaved"
:prevent-submit="true"
>
<template #form="{ data, validate }">
<span ref="closeButton" class="row justify-end close-icon" v-close-popup>
@ -303,7 +304,7 @@ async function getAmountPaid() {
:label="t('globals.save')"
:loading="formModelRef.isLoading"
color="primary"
type="submit"
@click="formModelRef.save()"
/>
</div>
</template>

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

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

View File

@ -5,14 +5,26 @@ import { useRoute } from 'vue-router';
import { dateRange } from 'src/filters';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import { toDateTimeFormat } from 'src/filters/date.js';
import VnDateBadge from 'src/components/common/VnDateBadge.vue';
import { dashIfEmpty } from 'src/filters';
import { toCurrency } from 'filters/index';
import { useArrayData } from 'composables/useArrayData';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import axios from 'axios';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
const { t } = useI18n();
const route = useRoute();
const from = ref();
const to = ref();
const hideInventory = ref(true);
const inventorySupplierFk = ref();
async function getInventorySupplier() {
inventorySupplierFk.value = (
await axios.get(`InventoryConfigs`)
)?.data[0]?.supplierFk;
}
const exprBuilder = (param, value) => {
switch (param) {
@ -33,25 +45,27 @@ const exprBuilder = (param, value) => {
}
};
const from = ref();
const to = ref();
const where = {
itemFk: route.params.id,
};
if (hideInventory.value) {
where.supplierFk = { neq: inventorySupplierFk };
}
const arrayData = useArrayData('ItemLastEntries', {
url: 'Items/lastEntriesFilter',
order: ['landed DESC', 'buyFk DESC'],
exprBuilder: exprBuilder,
userFilter: {
where: {
itemFk: route.params.id,
},
where: where,
},
});
const itemLastEntries = ref([]);
const columns = computed(() => [
{
label: t('lastEntries.ig'),
label: 'Nv',
name: 'ig',
align: 'center',
},
@ -59,33 +73,38 @@ const columns = computed(() => [
label: t('itemDiary.warehouse'),
name: 'warehouse',
field: 'warehouse',
align: 'left',
align: 'center',
},
{
label: t('lastEntries.landed'),
name: 'id',
name: 'date',
field: 'landed',
align: 'left',
format: (val) => toDateTimeFormat(val),
align: 'center',
},
{
label: t('lastEntries.entry'),
name: 'entry',
field: 'stateName',
align: 'left',
align: 'center',
format: (val) => dashIfEmpty(val),
},
{
label: t('lastEntries.pvp'),
name: 'pvp',
field: 'reference',
align: 'left',
align: 'center',
format: (_, row) => toCurrency(row.price2) + ' / ' + toCurrency(row.price3),
},
{
label: t('lastEntries.printedStickers'),
name: 'printedStickers',
field: 'printedStickers',
align: 'center',
format: (val) => dashIfEmpty(val),
},
{
label: t('lastEntries.label'),
name: 'label',
name: 'stickers',
field: 'stickers',
align: 'center',
format: (val) => dashIfEmpty(val),
@ -93,11 +112,13 @@ const columns = computed(() => [
{
label: t('shelvings.packing'),
name: 'packing',
field: 'packing',
align: 'center',
},
{
label: t('lastEntries.grouping'),
name: 'grouping',
field: 'grouping',
align: 'center',
},
{
@ -108,18 +129,19 @@ const columns = computed(() => [
},
{
label: t('lastEntries.quantity'),
name: 'stems',
name: 'quantity',
field: 'quantity',
align: 'center',
},
{
label: t('lastEntries.cost'),
name: 'cost',
align: 'left',
field: 'cost',
align: 'center',
},
{
label: t('lastEntries.kg'),
name: 'stems',
label: 'Kg',
name: 'weight',
field: 'weight',
align: 'center',
},
@ -131,9 +153,9 @@ const columns = computed(() => [
},
{
label: t('lastEntries.supplier'),
name: 'stems',
name: 'supplier',
field: 'supplier',
align: 'left',
align: 'center',
},
]);
@ -157,11 +179,18 @@ const updateFilter = async () => {
else if (from.value && !to.value) filter = { gte: from.value };
else if (from.value && to.value) filter = { between: [from.value, to.value] };
arrayData.store.userFilter.where.landed = filter;
const userFilter = arrayData.store.userFilter.where;
userFilter.landed = filter;
if (hideInventory.value) userFilter.supplierFk = { neq: inventorySupplierFk };
else delete userFilter.supplierFk;
await fetchItemLastEntries();
};
onMounted(async () => {
await getInventorySupplier();
const _from = Date.vnNew();
_from.setDate(_from.getDate() - 75);
from.value = getDate(_from, 'from');
@ -171,14 +200,13 @@ onMounted(async () => {
updateFilter();
watch([from, to], ([nFrom, nTo], [oFrom, oTo]) => {
watch([from, to, hideInventory], ([nFrom, nTo], [oFrom, oTo]) => {
if (nFrom && nFrom != oFrom) nFrom = getDate(new Date(nFrom), 'from');
if (nTo && nTo != oTo) nTo = getDate(new Date(nTo), 'to');
updateFilter();
});
});
</script>
<template>
<VnSubToolbar>
<template #st-data>
@ -187,27 +215,45 @@ onMounted(async () => {
dense
v-model="from"
class="q-mr-lg"
data-cy="from"
/>
<VnInputDate
:label="t('lastEntries.to')"
v-model="to"
dense
class="q-mr-lg"
data-cy="to"
/>
<QCheckbox
:label="t('Hide inventory supplier')"
v-model="hideInventory"
dense
class="q-mr-lg"
data-cy="hideInventory"
/>
<VnInputDate :label="t('lastEntries.to')" dense v-model="to" />
</template>
</VnSubToolbar>
<QPage class="column items-center q-pa-xd">
<QTable
:rows="itemLastEntries"
:columns="columns"
class="full-width q-mt-md"
class="table full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body-cell-ig="{ row }">
<QTd @click.stop>
<QCheckbox
v-model="row.isIgnored"
:disable="true"
:false-value="0"
:true-value="1"
<QTd class="text-center">
<QIcon
:name="row.isIgnored ? 'check_box' : 'check_box_outline_blank'"
style="color: var(--vn-label-color)"
size="sm"
/>
</QTd>
</template>
<template #body-cell-date="{ row }">
<QTd class="text-center">
<VnDateBadge :date="row.landed" />
</QTd>
</template>
<template #body-cell-entry="{ row }">
<QTd @click.stop>
<div class="full-width flex justify-center">
@ -229,8 +275,8 @@ onMounted(async () => {
</QTd>
</template>
<template #body-cell-pvp="{ value }">
<QTd @click.stop
><span> {{ value }}</span>
<QTd @click.stop class="text-center">
<span> {{ value }}</span>
<QTooltip>
{{ t('lastEntries.grouping') }}/{{ t('lastEntries.packing') }}
</QTooltip></QTd
@ -249,7 +295,7 @@ onMounted(async () => {
</QTd>
</template>
<template #body-cell-cost="{ row }">
<QTd @click.stop>
<QTd @click.stop class="text-center">
<span>
{{ toCurrency(row.cost, 'EUR', 3) }}
<QTooltip>
@ -267,10 +313,25 @@ onMounted(async () => {
</span>
</QTd>
</template>
<template #body-cell-supplier="{ row }">
<QTd @click.stop>
<div class="full-width flex justify-center">
<SupplierDescriptorProxy
:id="row.supplierFk"
class="q-ma-none"
dense
/>
<span class="link">{{ row.supplier }}</span>
</div>
</QTd>
</template>
</QTable>
</QPage>
</template>
<i18n>
es:
Hide inventory supplier: Ocultar proveedor inventario
</i18n>
<style lang="scss" scoped>
.q-badge--rounded {
border-radius: 50%;
@ -282,4 +343,10 @@ onMounted(async () => {
padding: 0 11px;
height: 28px;
}
.th :first-child {
.td {
text-align: center;
background-color: red;
}
}
</style>

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"

View File

@ -1,10 +1,9 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData';
import { toCurrency } from 'filters/index';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
@ -15,31 +14,22 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import ItemRequestFilter from './ItemRequestFilter.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const { t } = useI18n();
const { notify } = useNotify();
const stateStore = useStateStore();
let filterParams = ref({});
const denyFormRef = ref(null);
const denyRequestId = ref(null);
const denyRequestIndex = ref(null);
const itemRequestsOptions = ref([]);
const arrayData = useArrayData('ItemRequests', {
url: 'TicketRequests/filter',
userParams: filterParams,
order: ['shippedDate ASC', 'isOk ASC'],
});
const store = arrayData.store;
const userParams = {
state: 'pending',
daysOnward: 7,
};
const tableRef = ref();
watch(
() => store.data,
(value) => (itemRequestsOptions.value = value)
);
onMounted(async () => {
stateStore.rightDrawer = true;
});
const columns = computed(() => [
{
@ -223,11 +213,6 @@ const onDenyAccept = (_, responseData) => {
denyRequestIndex.value = null;
tableRef.value.reload();
};
onMounted(async () => {
await arrayData.fetch({ append: false });
stateStore.rightDrawer = true;
});
</script>
<template>
@ -244,10 +229,10 @@ onMounted(async () => {
:columns="columns"
:user-params="userParams"
:is-editable="true"
:right-search="false"
auto-load
:disable-option="{ card: true }"
chip-locale="item.params"
:right-search="false"
>
<template #column-ticketFk="{ row }">
<span class="link">
@ -306,14 +291,17 @@ onMounted(async () => {
<template #moreFilterPanel="{ params }">
<VnInputNumber
:label="t('params.scopeDays')"
v-model.number="params.scopeDays"
v-model.number="params.daysOnward"
@keyup.enter="(evt) => handleScopeDays(evt.target.value)"
@remove="handleScopeDays()"
class="q-px-xs q-pr-lg"
filled
dense
lazy-rules
is-outlined
/>
</template>
<template #column-denyOptions="{ row, rowIndex }">
<QIcon
v-if="row.response?.length"

View File

@ -6,7 +6,6 @@ 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 VnInputDate from 'src/components/common/VnInputDate.vue';
import { useArrayData } from 'src/composables/useArrayData';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
@ -46,20 +45,6 @@ const exprBuilder = (param, value) => {
}
};
const add = (paramsObj, key) => {
if (paramsObj[key] === undefined) {
paramsObj[key] = 1;
} else {
paramsObj[key]++;
}
};
const decrement = (paramsObj, key) => {
if (paramsObj[key] === 0) return;
paramsObj[key]--;
};
onMounted(async () => {
if (arrayData.store?.userParams) {
fieldFiltersValues.value = Object.entries(arrayData.store.userParams).map(
@ -95,8 +80,7 @@ onMounted(async () => {
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span v-if="tag.label !== 'state'">{{ formatFn(tag.value) }}</span>
<span v-else>{{ t(`${tag.value}`) }}</span>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
@ -174,83 +158,6 @@ onMounted(async () => {
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.myTeam"
:label="t('params.myTeam')"
@update:model-value="searchFn()"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QCard bordered>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.from')"
v-model="params.from"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.to')"
v-model="params.to"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.scopeDays"
:label="t('globals.daysOnward')"
type="number"
dense
outlined
rounded
:min="0"
>
<template #append>
<QBtn
icon="add"
flat
dense
size="12px"
shortcut="+"
@click="add(params, 'scopeDays')"
/>
<QBtn
icon="remove"
flat
dense
size="12px"
@click="decrement(params, 'scopeDays')"
/>
</template>
</VnInput>
</QItemSection>
</QItem>
<QIcon name="info" style="position: absolute; top: 4px; right: 4px">
<QTooltip max-width="300px">
{{ t('dateFiltersTooltip') }}
</QTooltip>
</QIcon>
</QCard>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.mine')"
v-model="params.mine"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
@ -267,6 +174,16 @@ onMounted(async () => {
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('params.daysOnward')"
v-model="params.daysOnward"
lazy-rules
is-outlined
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>

View File

@ -78,29 +78,32 @@ async function setItemTypeData(data) {
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" />
</router-link>
<VnLv :label="t('summary.id')" :value="itemType.id" />
<VnLv :label="t('shared.code')" :value="itemType.code" />
<VnLv :label="t('shared.name')" :value="itemType.name" />
<VnLv :label="t('shared.worker')">
<VnLv :label="t('itemType.summary.id')" :value="itemType.id" />
<VnLv :label="t('itemType.shared.code')" :value="itemType.code" />
<VnLv :label="t('itemType.shared.name')" :value="itemType.name" />
<VnLv :label="t('itemType.shared.worker')">
<template #value>
<span class="link">{{ itemType.worker?.firstName }}</span>
<WorkerDescriptorProxy :id="itemType.worker?.id" />
</template>
</VnLv>
<VnLv :label="t('shared.category')" :value="itemType.category?.name" />
<VnLv
:label="t('shared.temperature')"
:label="t('itemType.shared.category')"
:value="itemType.category?.name"
/>
<VnLv
:label="t('itemType.shared.temperature')"
:value="itemType.temperature?.name"
/>
<VnLv :label="t('summary.life')" :value="itemType.life" />
<VnLv :label="t('summary.promo')" :value="itemType.promo" />
<VnLv :label="t('itemType.summary.life')" :value="itemType.life" />
<VnLv :label="t('itemType.summary.promo')" :value="itemType.promo" />
<VnLv
:label="t('summary.itemPackingType')"
:label="t('itemType.summary.itemPackingType')"
:value="itemType.itemPackingType?.description"
/>
<VnLv
class="large-label"
:label="t('summary.isUnconventionalSize')"
:label="t('itemType.summary.isUnconventionalSize')"
:value="itemType.isUnconventionalSize"
/>
</QCard>

View File

@ -1,16 +1,17 @@
shared:
code: Code
name: Name
worker: Worker
category: Category
temperature: Temperature
life: Life
itemPackingType: Item packing type
maxRefs: Maximum references
fragile: Fragile
summary:
id: id
life: Life
promo: Promo
itemPackingType: Item packing type
isUnconventionalSize: Is unconventional size
itemType:
shared:
code: Code
name: Name
worker: Worker
category: Category
temperature: Temperature
life: Life
itemPackingType: Item packing type
maxRefs: Maximum references
fragile: Fragile
summary:
id: id
life: Life
promo: Promo
itemPackingType: Item packing type
isUnconventionalSize: Is unconventional size

View File

@ -1,16 +1,17 @@
shared:
code: Código
name: Nombre
worker: Trabajador
category: Reino
temperature: Temperatura
life: Vida
itemPackingType: Tipo de embalaje
maxRefs: Referencias máximas
fragile: Frágil
summary:
id: id
life: Vida
promo: Promoción
itemPackingType: Tipo de embalaje
isUnconventionalSize: Es de tamaño poco convencional
itemType:
shared:
code: Código
name: Nombre
worker: Trabajador
category: Reino
temperature: Temperatura
life: Vida
itemPackingType: Tipo de embalaje
maxRefs: Referencias máximas
fragile: Frágil
summary:
id: id
life: Vida
promo: Promoción
itemPackingType: Tipo de embalaje
isUnconventionalSize: Es de tamaño poco convencional

View File

@ -66,6 +66,7 @@ lastEntries:
package: Package
freight: Freight
comission: Comission
printedStickers: Pri.
itemTags:
removeTag: Remove tag
addTag: Add tag

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

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
@ -10,14 +10,15 @@ 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);
@ -26,7 +27,7 @@ const stateOpts = ref([]);
const zoneOpts = ref([]);
const DepartmentOpts = ref([]);
const ItemPackingTypeOpts = ref([]);
const visibleColumns = ref([]);
const stateStore = useStateStore();
const { viewSummary } = useSummaryDialog();
const [from, to] = dateRange(Date.vnNew());
@ -37,6 +38,11 @@ const stateColors = {
alert: 'negative',
};
onMounted(() => {
stateStore.leftDrawer = false;
stateStore.rightDrawer = false;
});
function exprBuilder(param, value) {
switch (param) {
case 'stateFk':
@ -293,19 +299,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) => {
@ -322,14 +315,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>
@ -443,13 +428,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

@ -1,7 +1,7 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import { useRoute, useRouter } from 'vue-router';
import { onMounted, 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';
@ -75,19 +75,6 @@ watch(
},
{ immediate: true }
);
const onItemSaved = (updatedItem) => {
requestAnimationFrame(() => {
scrollToItem(updatedItem.items[0].itemFk);
});
};
const scrollToItem = async (id) => {
const element = itemRefs.value[id]?.$el;
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
};
provide('onItemSaved', onItemSaved);
</script>
<template>
@ -100,16 +87,14 @@ provide('onItemSaved', onItemSaved);
:label="t('Search items')"
:info="t('You can search items by name or id')"
/>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<OrderCatalogFilter
:data-key="dataKey"
:tag-value="tagValue"
:tags="tags"
:initial-catalog-params="catalogParams"
/>
</QScrollArea>
</QDrawer>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<OrderCatalogFilter
:data-key="dataKey"
:tag-value="tagValue"
:tags="tags"
:initial-catalog-params="catalogParams"
/>
</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, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useRoute } from 'vue-router';
import useNotify from 'composables/useNotify';
import { useArrayData } from 'composables/useArrayData';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { useState } from 'src/composables/useState';
const { t } = useI18n();
const { notify } = useNotify();
@ -18,10 +18,17 @@ const props = defineProps({
required: true,
},
});
const onItemSaved = inject('onItemSaved');
const state = useState();
const orderData = computed(() => state.get('orderData'));
const prices = ref((props.item.prices || []).map((item) => ({ ...item, quantity: 0 })));
const descriptorData = useArrayData('orderData');
const isLoading = ref(false);
const totalQuantity = (items) =>
items.reduce((acc, item) => {
return acc + item.quantity;
}, 0);
const addToOrder = async () => {
if (isLoading.value) return;
isLoading.value = true;
@ -30,10 +37,19 @@ const addToOrder = async () => {
items,
orderFk: Number(route.params.id),
});
const { data: orderTotal } = await axios.get(
`Orders/${Number(route.params.id)}/getTotal`
);
state.set('orderTotal', orderTotal);
const rows = orderData.value.rows.push(...items) || [];
state.set('orderData', {
...orderData.value,
rows,
});
notify(t('globals.dataSaved'), 'positive');
await descriptorData.fetch({});
onItemSaved({ ...props, items, saved: true });
emit('added', items);
emit('added', -totalQuantity(items));
isLoading.value = false;
};
const canAddToOrder = () => {

View File

@ -63,21 +63,26 @@ const setData = (entity) => {
if (!entity) return;
getTotalRef.value && getTotalRef.value.fetch();
data.value = useCardDescription(entity?.client?.name, entity?.id);
state.set('orderData', entity);
state.set('orderTotal', total);
};
const getConfirmationValue = (isConfirmed) => {
return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed');
};
const total = ref(null);
const orderTotal = computed(() => state.get('orderTotal') ?? 0);
const total = ref(0);
</script>
<template>
<FetchData
ref="getTotalRef"
:url="`Orders/${entityId}/getTotal`"
@on-fetch="(response) => (total = response)"
@on-fetch="
(response) => {
total = response;
}
"
/>
<CardDescriptor
ref="descriptor"
@ -112,7 +117,7 @@ const total = ref(null);
:label="t('order.summary.items')"
:value="(entity?.rows?.length || DEFAULT_ITEMS).toString()"
/>
<VnLv :label="t('order.summary.total')" :value="toCurrency(total)" />
<VnLv :label="t('order.summary.total')" :value="toCurrency(orderTotal)" />
</template>
<template #actions="{ entity }">
<QCardActions>

View File

@ -251,7 +251,7 @@ watch(
@on-fetch="(data) => (orderSummary.vat = data)"
auto-load
/>
<QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<QCard
class="order-lines-summary q-pa-lg"
v-if="orderSummary.vat && orderSummary.total"
@ -266,7 +266,7 @@ watch(
<VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" />
<VnLv :label="t('total') + ': '" :value="toCurrency(orderSummary?.total)" />
</QCard>
</QDrawer>
</Teleport>
<VnTable
ref="tableLinesRef"

View File

@ -8,7 +8,7 @@ import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import SupplierConsumptionFilter from './SupplierConsumptionFilter.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import { toDate } from 'src/filters';
import { dateRange, toDate } from 'src/filters';
import { dashIfEmpty } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import useNotify from 'src/composables/useNotify.js';
@ -35,14 +35,17 @@ const store = arrayData.store;
onUnmounted(() => state.unset('SupplierConsumption'));
const dateRanges = computed(() => {
const { from, to } = arrayData.store?.userParams || {};
let { from, to } = arrayData.store?.userParams || {};
return { from, to };
});
const reportParams = computed(() => ({
recipientId: Number(route.params.id),
...dateRanges.value,
}));
const reportParams = computed(() => {
return {
recipientId: Number(route.params.id),
to: dateRange(dateRanges.value.to)[1],
from: dateRange(dateRanges.value.from)[1],
};
});
async function getSupplierConsumptionData() {
await arrayData.fetch({ append: false });
@ -171,11 +174,9 @@ onMounted(async () => {
</div>
</Teleport>
<QPage class="column items-center q-pa-md">
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<SupplierConsumptionFilter data-key="SupplierConsumption" />
</QScrollArea>
</QDrawer>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<SupplierConsumptionFilter data-key="SupplierConsumption" />
</Teleport>
<QTable
:rows="rows"
row-key="id"

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import { ref, computed, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
@ -10,37 +10,15 @@ import { useStateStore } from 'stores/useStateStore';
import { toCurrency } from 'filters/index';
import { useRole } from 'src/composables/useRole';
const $props = defineProps({
formData: {
type: Object,
required: true,
},
haveNegatives: {
type: Boolean,
required: true,
default: false,
},
});
const emit = defineEmits(['updateForm', 'update:haveNegatives']);
const haveNegatives = defineModel('haveNegatives', { type: Boolean, required: true });
const formData = defineModel({ type: Object, required: true });
const stateStore = useStateStore();
const { t } = useI18n();
const { hasAny } = useRole();
const _ticketData = ref($props.formData);
const ticketUpdateActions = ref(null);
const haveNegatives = computed({
get: () => $props.haveNegatives,
set: (val) => emit('update:haveNegatives', val),
});
const rows = computed(() => _ticketData.value?.sale?.items || []);
watch(
() => _ticketData.value,
(val) => emit('updateForm', val),
{ deep: true }
);
const rows = computed(() => formData.value?.sale?.items || []);
const columns = computed(() => [
{
@ -57,24 +35,28 @@ const columns = computed(() => [
align: 'left',
hidden: true,
},
{
name: 'subName',
align: 'left',
required: true,
},
{
label: t('basicData.movable'),
name: 'movable',
align: 'left',
},
{
required: true,
label: t('basicData.quantity'),
name: 'quantity',
field: 'quantity',
align: 'left',
classes: 'number',
},
{
required: true,
label: t('basicData.pricePPU'),
name: 'price',
field: 'price',
align: 'left',
classes: 'number',
format: (val) => toCurrency(val),
},
{
@ -82,7 +64,7 @@ const columns = computed(() => [
label: t('basicData.newPricePPU'),
name: 'newPrice',
field: (row) => row.component.newPrice,
align: 'left',
classes: 'number',
format: (val) => toCurrency(val),
},
{
@ -90,14 +72,15 @@ const columns = computed(() => [
label: t('basicData.difference'),
name: 'difference',
field: (row) => row.component.difference,
align: 'left',
classes: 'number',
format: (val) => toCurrency(val),
autoWidth: true,
},
]);
const loadDefaultTicketAction = () => {
const isSalesAssistant = hasAny(['salesAssistant']);
_ticketData.value.option = isSalesAssistant ? 'mana' : 'renewPrices';
formData.value.option = isSalesAssistant ? 'mana' : 'renewPrices';
};
const totalPrice = computed(() => {
@ -115,24 +98,25 @@ const totalDifference = computed(() => {
return rows.value.reduce((acc, item) => acc + item.component?.difference || 0, 0);
});
const showMovableColumn = computed(() => (haveDifferences.value > 0 ? ['movable'] : []));
const haveDifferences = computed(() => _ticketData.value.sale?.haveDifferences);
const ticketHaveNegatives = () => {
const haveDifferences = computed(() => formData.value.sale?.haveDifferences);
async function ticketHaveNegatives() {
let _haveNegatives = false;
let haveNotNegatives = false;
_ticketData.value.withoutNegatives = false;
_ticketData.value?.sale?.items.forEach((item) => {
formData.value.withoutNegatives = false;
formData.value?.sale?.items.forEach((item) => {
if (item.quantity > item.movable) _haveNegatives = true;
else haveNotNegatives = true;
});
haveNegatives.value = _haveNegatives && haveNotNegatives && haveDifferences.value;
if (haveNegatives.value) _ticketData.value.withoutNegatives = true;
};
await nextTick();
if (haveNegatives.value) formData.value.withoutNegatives = true;
}
onMounted(() => {
onMounted(async () => {
stateStore.rightDrawer = true;
loadDefaultTicketAction();
ticketHaveNegatives();
await ticketHaveNegatives();
});
</script>
@ -189,7 +173,7 @@ onMounted(() => {
horizontal
>
<QRadio
v-model="_ticketData.option"
v-model="formData.option"
:val="action.code"
:label="action.description"
dense
@ -206,7 +190,7 @@ onMounted(() => {
<QCardSection horizontal class="flex row items-center">
<QCheckbox
:label="t('basicData.withoutNegatives')"
v-model="_ticketData.withoutNegatives"
v-model="formData.withoutNegatives"
:toggle-indeterminate="false"
/>
<QIcon name="info" size="xs" class="q-ml-sm">
@ -223,7 +207,7 @@ onMounted(() => {
:columns="columns"
row-key="id"
:pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md"
class="full-width"
:no-data-label="t('globals.noResults')"
flat
>
@ -236,21 +220,27 @@ onMounted(() => {
</QTd>
</template>
<template #body-cell-description="{ row }">
<QTd style="display: contents">
<div class="column">
<QTd style="min-width: 120px; max-width: 120px">
<div class="column q-pb-xs" style="min-width: 120px">
<span>{{ row.item.name }}</span>
<span class="color-vn-label">{{ row.item.subName }}</span>
<FetchedTags :item="row.item" />
<FetchedTags :item="row.item" class="full-width" />
</div>
</QTd>
</template>
<template #body-cell-movable="{ row }">
<template #body-cell-subName="{ row }">
<QTd>
<QBadge
v-if="_ticketData?.sale?.haveDifferences"
<span class="color-vn-label">{{ row.item.subName }}</span>
</QTd>
</template>
<template #body-cell-movable="{ row }">
<QTd class="number">
<QChip
v-if="formData?.sale?.haveDifferences"
:text-color="row.quantity > row.movable ? 'black' : 'white'"
:color="row.quantity > row.movable ? 'negative' : 'transparent'"
:label="row.movable"
dense
square
/>
</QTd>
</template>

View File

@ -21,7 +21,6 @@ const formData = defineModel({
required: true,
});
const emit = defineEmits(['updateForm']);
const { validate } = useValidator();
const { notify } = useNotify();
const router = useRouter();
@ -33,18 +32,11 @@ const canEditZone = useAcl().hasAny([
const agencyFetchRef = ref();
const warehousesOptions = ref([]);
const companiesOptions = ref([]);
const currenciesOptions = ref([]);
const agenciesOptions = ref([]);
const zonesOptions = ref([]);
const addresses = ref([]);
const zoneSelectRef = ref();
watch(
() => formData.value,
(val) => emit('updateForm', val),
{ deep: true }
);
onMounted(() => onFormModelInit());
const agencyByWarehouseFilter = computed(() => ({

View File

@ -155,18 +155,10 @@ onBeforeMount(async () => await getTicketData());
}"
>
<QStep :name="1" :title="t('globals.pageTitles.basicData')" :done="step > 1">
<TicketBasicDataForm
v-if="initialDataLoaded"
@update-form="($event) => (formData = $event)"
v-model="formData"
/>
<TicketBasicDataForm v-if="initialDataLoaded" v-model="formData" />
</QStep>
<QStep :name="2" :title="t('basicData.priceDifference')">
<TicketBasicData
:form-data="formData"
v-model:haveNegatives="haveNegatives"
@update-form="($event) => (formData = $event)"
/>
<TicketBasicData v-model="formData" v-model:have-negatives="haveNegatives" />
</QStep>
<template #navigation>
<QStepperNavigation class="flex justify-between">

View File

@ -1,6 +1,7 @@
<script setup>
import axios from 'axios';
import { date, useQuasar } from 'quasar';
import { useStateStore } from 'src/stores/useStateStore';
import { computed, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -8,7 +9,7 @@ import { useRouter } from 'vue-router';
const router = useRouter();
const { t } = useI18n();
const quasar = useQuasar();
const stateStore = useStateStore();
onMounted(async () => {
await fetch();
});
@ -84,7 +85,7 @@ async function getVideoList(expeditionId, timed) {
</script>
<template>
<QDrawer show-if-above side="right">
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<QScrollArea class="fit">
<QList bordered separator style="max-width: 318px">
<QItem v-if="lastExpedition && videoList.length">
@ -150,7 +151,7 @@ async function getVideoList(expeditionId, timed) {
</QItem>
</QList>
</QScrollArea>
</QDrawer>
</Teleport>
<QCard>
<QCarousel animated v-model="slide" height="max-content">

View File

@ -178,7 +178,7 @@ onMounted(() => {
@on-fetch="(data) => (components = data)"
auto-load
/>
<QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black">
<QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width">
@ -264,7 +264,7 @@ onMounted(() => {
<span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span>
</QCardSection>
</QCard>
</QDrawer>
</Teleport>
<VnTable
ref="tableRef"
data-key="TicketComponents"

View File

@ -17,6 +17,7 @@ import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnBtnSelect from 'src/components/common/VnBtnSelect.vue';
import FetchData from 'src/components/FetchData.vue';
import useOpenURL from 'src/composables/useOpenURL';
const route = useRoute();
const stateStore = useStateStore();
@ -123,6 +124,12 @@ const columns = computed(() => [
isPrimary: true,
action: (row) => showLog(row),
},
{
title: t('Grafana'),
icon: 'vn:grafana',
isPrimary: true,
action: ({ id }) => openGrafana(id),
},
],
},
]);
@ -192,10 +199,16 @@ const getExpeditionState = async (expedition) => {
}));
};
const openGrafana = (expeditionFk) => {
useOpenURL(
`https://grafana.verdnatura.es/d/d552ab74-85b4-4e7f-a279-fab7cd9c6124/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}`
);
};
onMounted(async () => {
stateStore.rightDrawer = true;
const filteredColumns = columns.value.filter((col) => col.name !== 'history');
allColumnNames.value = filteredColumns.map((col) => col.name);
const filteredColumns = columns.value.filter(({ name }) => name !== 'history');
allColumnNames.value = filteredColumns.map(({ name }) => name);
});
</script>

View File

@ -617,7 +617,7 @@ watch(
</QBtnGroup>
</template>
</VnSubToolbar>
<QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<div
class="q-pa-md q-mb-md q-ma-md color-vn-text"
style="border: 2px solid black"
@ -638,8 +638,8 @@ watch(
<span class="q-mr-xs color-vn-label"> {{ t('basicData.total') }}: </span>
<span>{{ toCurrency(store.data?.totalWithVat) }}</span>
</QCardSection>
</div></QDrawer
>
</div>
</Teleport>
<VnTable
ref="tableRef"
data-key="TicketSales"

View File

@ -100,11 +100,9 @@ onMounted(() => (stateStore.rightDrawer = true));
@on-fetch="(data) => applyVolumes(data)"
auto-load
/>
<QDrawer
v-if="packingTypeVolume.length"
side="right"
:width="265"
v-model="stateStore.rightDrawer"
<Teleport
to="#right-panel"
v-if="stateStore.isHeaderMounted() && packingTypeVolume.length"
>
<QCard
v-for="(packingType, index) in packingTypeVolume"
@ -124,8 +122,7 @@ onMounted(() => (stateStore.rightDrawer = true));
<span> {{ t('volume.volume') }}: {{ packingType.volume }} </span>
</QCardSection>
</QCard>
</QDrawer>
</Teleport>
<VnTable
ref="tableRef"
data-key="TicketVolume"

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { computed, ref, onMounted } from 'vue';
import { computed, ref, onBeforeMount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n';
@ -46,7 +46,7 @@ const userParams = {
from: null,
to: null,
};
onMounted(() => {
onBeforeMount(() => {
initializeFromQuery();
stateStore.rightDrawer = true;
if (!route.query.createForm) return;

View File

@ -15,9 +15,6 @@ const columns = computed(() => [
name: 'paymentDate',
label: t('worker.balance.tableVisibleColumns.paymentDate'),
create: true,
columnCreate: {
required: true,
},
component: 'date',
field: 'paymentDate',
cardVisible: true,
@ -27,9 +24,6 @@ const columns = computed(() => [
name: 'incomeTypeFk',
label: t('worker.balance.tableVisibleColumns.incomeType'),
create: true,
columnCreate: {
required: true,
},
component: 'select',
attrs: {
options: payrollComponents,
@ -43,9 +37,6 @@ const columns = computed(() => [
name: 'debit',
label: t('worker.balance.tableVisibleColumns.debit'),
create: true,
columnCreate: {
required: true,
},
component: 'input',
field: 'debit',
cardVisible: true,
@ -55,9 +46,6 @@ const columns = computed(() => [
name: 'credit',
label: t('worker.balance.tableVisibleColumns.credit'),
create: true,
columnCreate: {
required: true,
},
component: 'input',
field: 'credit',
cardVisible: true,

View File

@ -10,6 +10,7 @@ import { useState } from 'src/composables/useState';
import axios from 'axios';
import VnImg from 'src/components/ui/VnImg.vue';
import EditPictureForm from 'components/EditPictureForm.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
const $props = defineProps({
id: {
@ -143,10 +144,14 @@ const handlePhotoUpdated = (evt = false) => {
:value="entity.user?.emailUser?.email"
copy
/>
<VnLv
:label="t('worker.list.department')"
:value="entity.department ? entity.department.department.name : null"
/>
<VnLv :label="t('worker.list.department')">
<template #value>
<span class="link" v-text="entity.department?.department?.name" />
<DepartmentDescriptorProxy
:id="entity.department?.department?.id"
/>
</template>
</VnLv>
<VnLv :value="entity.phone">
<template #label>
{{ t('globals.phone') }}

View File

@ -23,6 +23,7 @@ const insertTag = () => {
workerPitCrudRef.value.insert();
};
const quasar = useQuasar();
const deleteRelative = async (id) => {
await new Promise((resolve) => {
quasar
@ -62,7 +63,6 @@ const deleteRelative = async (id) => {
>
<template #form="{ data }">
<QCard class="q-px-lg q-py-lg">
<VnTitle :text="t('IRPF')" />
<VnRow>
<VnInput
:label="t('familySituation')"
@ -100,8 +100,12 @@ const deleteRelative = async (id) => {
:label="t(`spousePension`)"
/>
</VnRow>
<VnRow>
<QCheckbox v-model="data.isDependend" :label="t(`isDependend`)" />
<VnRow wrap>
<QCheckbox
clearable
v-model="data.isDependend"
:label="t(`isDependend`)"
/>
<QCheckbox
v-model="data.hasHousingPaymentBefore"
:label="t(`hasHousingPaymentBefore`)"
@ -127,7 +131,11 @@ const deleteRelative = async (id) => {
:filter="{
where: { workerFk: route.params.id },
}"
:data-required="{ workerFk: route.params.id }"
:data-required="{
workerFk: route.params.id,
isJointCustody: false,
isDependend: false,
}"
:has-sub-toolbar="false"
>
<template #body="{ rows }">

View File

@ -108,7 +108,20 @@ const agencyOptions = ref([]);
clearable
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('Distribution point')"
v-model="data.addressFk"
option-value="id"
option-label="nickname"
url="Addresses"
:fields="['id', 'nickname']"
sort-by="id"
hide-selected
map-options
:rules="validate('data.addressFk')"
/>
</VnRow>
<VnRow>
<VnInput
v-model="data.inflation"
@ -143,4 +156,5 @@ es:
Inflation: Inflación
Volumetric: Volumétrico
Max length : Medida máxima tumbado
Distribution point: Punto de distribución
</i18n>

View File

@ -16,24 +16,14 @@ const actions = {
clone: async () => {
const opts = { message: t('Zone cloned'), type: 'positive' };
try {
const { data } = await axios.post(`Zones/${zoneId}/clone`, {});
notify(opts);
push(`/zone/${data.id}/basic-data`);
} catch (e) {
opts.message = t('It was not able to clone the zone');
opts.type = 'negative';
}
const { data } = await axios.post(`Zones/${zoneId}/clone`, {});
notify(opts);
push(`/zone/${data.id}/basic-data`);
},
remove: async () => {
try {
await axios.post(`Zones/${zoneId}/deleteZone`);
notify({ message: t('Zone deleted'), type: 'positive' });
push({ name: 'ZoneList' });
} catch (e) {
notify({ message: e.message, type: 'negative' });
}
await axios.post(`Zones/${zoneId}/deleteZone`);
notify({ message: t('Zone deleted'), type: 'positive' });
push({ name: 'ZoneList' });
},
};
function openConfirmDialog(callback) {

View File

@ -43,16 +43,14 @@ const onZoneEventFormClose = () => {
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<ZoneEventsPanel
:first-day="firstDay"
:last-day="lastDay"
:events="events"
v-model:formModeName="formModeName"
/>
</QScrollArea>
</QDrawer>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<ZoneEventsPanel
:first-day="firstDay"
:last-day="lastDay"
:events="events"
v-model:formModeName="formModeName"
/>
</Teleport>
<QPage class="q-pa-md flex justify-center">
<ZoneCalendarGrid
v-model:events="events"

View File

@ -11,10 +11,6 @@ import { dashIfEmpty } from 'src/filters';
import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import { useVnConfirm } from 'composables/useVnConfirm';
const formModeName = defineModel('formModeName', {
type: String,
required: true,
});
const props = defineProps({
firstDay: {
type: Date,
@ -31,8 +27,18 @@ const props = defineProps({
required: true,
default: () => [],
},
formModeName: {
type: String,
required: true,
default: 'include',
},
});
const formName = computed({
get: () => props.formModeName,
set: (value) => emit('update:formModeName', value),
});
const emit = defineEmits(['openZoneForm', 'update:formModeName']);
const { t } = useI18n();
const route = useRoute();
const weekdayStore = useWeekdayStore();
@ -80,6 +86,15 @@ const deleteEvent = async (id) => {
await fetchData();
};
const openInclusionForm = (event) => {
formName.value = 'include';
emit('openZoneForm', {
date: event.dated,
event,
isNewMode: false,
});
};
onMounted(async () => {
weekdayStore.initStore();
});
@ -92,13 +107,13 @@ onMounted(async () => {
t('eventsPanel.editMode')
}}</span>
<QRadio
v-model="formModeName"
v-model="formName"
dense
val="include"
:label="t('eventsPanel.include')"
/>
<QRadio
v-model="formModeName"
v-model="formName"
dense
val="exclude"
:label="t('eventsPanel.exclude')"

View File

@ -20,6 +20,7 @@ export default {
path: '',
name: 'MonitorMain',
component: () => import('src/components/common/VnSectionMain.vue'),
props: (route) => ({ leftDrawer: route.name === 'MonitorClientsActions' }),
redirect: { name: 'MonitorTickets' },
children: [
{

View File

@ -0,0 +1,20 @@
describe('ItemLastEntries', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('buyer');
cy.visit('/#/item/1/last-entries');
cy.intercept('GET', /.*lastEntriesFilter/).as('item');
cy.waitForElement('tbody');
});
it('should filter by agency', () => {
cy.get('tbody > tr')
.its('length')
.then((rowCount) => {
cy.get('[data-cy="hideInventory"]').click();
cy.wait('@item');
cy.waitForElement('tbody');
cy.get('tbody > tr').should('have.length.greaterThan', rowCount);
});
});
});

View File

@ -0,0 +1,28 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import VnDiscount from 'components/common/vnDiscount.vue';
describe('VnDiscount', () => {
let vm;
beforeAll(() => {
vm = createWrapper(VnDiscount, {
props: {
data: {},
price: 100,
quantity: 2,
discount: 10,
}
}).vm;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('total', () => {
it('should calculate total correctly', () => {
expect(vm.total).toBe(180);
});
});
});