Merge branch 'dev' into 8162-E2ETickets
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Javier Segarra 2024-11-21 08:20:29 +00:00
commit 81d7b9f04f
51 changed files with 477 additions and 551 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.44.0", "version": "24.50.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",
@ -64,4 +64,4 @@
"vite": "^5.1.4", "vite": "^5.1.4",
"vitest": "^0.31.1" "vitest": "^0.31.1"
} }
} }

View File

@ -9,8 +9,6 @@ import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
defineProps({ showEntityField: { type: Boolean, default: true } });
const emit = defineEmits(['onDataSaved']); const emit = defineEmits(['onDataSaved']);
const { t } = useI18n(); const { t } = useI18n();
const bicInputRef = ref(null); const bicInputRef = ref(null);
@ -18,17 +16,16 @@ const state = useState();
const customer = computed(() => state.get('customer')); const customer = computed(() => state.get('customer'));
const countriesFilter = {
fields: ['id', 'name', 'code'],
};
const bankEntityFormData = reactive({ const bankEntityFormData = reactive({
name: null, name: null,
bic: null, bic: null,
countryFk: customer.value?.countryFk, countryFk: customer.value?.countryFk,
id: null,
}); });
const countriesFilter = {
fields: ['id', 'name', 'code'],
};
const countriesOptions = ref([]); const countriesOptions = ref([]);
const onDataSaved = (...args) => { const onDataSaved = (...args) => {
@ -44,7 +41,6 @@ onMounted(async () => {
<template> <template>
<FetchData <FetchData
url="Countries" url="Countries"
:filter="countriesFilter"
auto-load auto-load
@on-fetch="(data) => (countriesOptions = data)" @on-fetch="(data) => (countriesOptions = data)"
/> />
@ -54,6 +50,7 @@ onMounted(async () => {
:title="t('title')" :title="t('title')"
:subtitle="t('subtitle')" :subtitle="t('subtitle')"
:form-initial-data="bankEntityFormData" :form-initial-data="bankEntityFormData"
:filter="countriesFilter"
@on-data-saved="onDataSaved" @on-data-saved="onDataSaved"
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
@ -85,7 +82,13 @@ onMounted(async () => {
:rules="validate('bankEntity.countryFk')" :rules="validate('bankEntity.countryFk')"
/> />
</div> </div>
<div v-if="showEntityField" class="col"> <div
v-if="
countriesOptions.find((c) => c.id === data.countryFk)?.code ==
'ES'
"
class="col"
>
<VnInput <VnInput
:label="t('id')" :label="t('id')"
v-model="data.id" v-model="data.id"

View File

@ -1,155 +0,0 @@
<script setup>
import { reactive, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue';
import VnInputDate from './common/VnInputDate.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const router = useRouter();
const manualInvoiceFormData = reactive({
maxShipped: Date.vnNew(),
});
const formModelPopupRef = ref();
const invoiceOutSerialsOptions = ref([]);
const taxAreasOptions = ref([]);
const ticketsOptions = ref([]);
const clientsOptions = ref([]);
const isLoading = computed(() => formModelPopupRef.value?.isLoading);
const onDataSaved = async (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
if (requestResponse && requestResponse.id)
router.push({ name: 'InvoiceOutSummary', params: { id: requestResponse.id } });
};
</script>
<template>
<FetchData
url="InvoiceOutSerials"
:filter="{ where: { code: { neq: 'R' } }, order: ['code'] }"
@on-fetch="(data) => (invoiceOutSerialsOptions = data)"
auto-load
/>
<FetchData
url="TaxAreas"
:filter="{ order: ['code'] }"
@on-fetch="(data) => (taxAreasOptions = data)"
auto-load
/>
<FormModelPopup
ref="formModelPopupRef"
:title="t('Create manual invoice')"
url-create="InvoiceOuts/createManualInvoice"
model="invoiceOut"
:form-initial-data="manualInvoiceFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<span v-if="isLoading" class="text-primary invoicing-text">
<QIcon name="warning" class="fill-icon q-mr-sm" size="md" />
{{ t('Invoicing in progress...') }}
</span>
<VnRow>
<VnSelect
:label="t('Ticket')"
:options="ticketsOptions"
hide-selected
option-label="id"
option-value="id"
v-model="data.ticketFk"
@update:model-value="data.clientFk = null"
url="Tickets"
:where="{ refFk: null }"
:fields="['id', 'nickname']"
:filter-options="{ order: 'shipped DESC' }"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel caption>{{ scope.opt?.nickname }}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<span class="row items-center" style="max-width: max-content">{{
t('Or')
}}</span>
<VnSelect
:label="t('Client')"
:options="clientsOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.clientFk"
@update:model-value="data.ticketFk = null"
url="Clients"
:fields="['id', 'name']"
:filter-options="{ order: 'name ASC' }"
/>
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</VnRow>
<VnRow>
<VnSelect
:label="t('Serial')"
:options="invoiceOutSerialsOptions"
hide-selected
option-label="description"
option-value="code"
v-model="data.serial"
/>
<VnSelect
:label="t('Area')"
:options="taxAreasOptions"
hide-selected
option-label="code"
option-value="code"
v-model="data.taxArea"
/>
</VnRow>
<VnRow>
<VnInput
:label="t('Reference')"
type="textarea"
v-model="data.reference"
fill-input
autogrow
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<style lang="scss" scoped>
.invoicing-text {
display: flex;
justify-content: center;
align-items: center;
color: $primary;
font-size: 24px;
margin-bottom: 8px;
}
</style>
<i18n>
es:
Create manual invoice: Crear factura manual
Ticket: Ticket
Client: Cliente
Max date: Fecha límite
Serial: Serie
Area: Area
Reference: Referencia
Or: O
Invoicing in progress...: Facturación en progreso...
</i18n>

View File

@ -0,0 +1,40 @@
<script setup>
defineProps({ row: { type: Object, required: true } });
</script>
<template>
<span>
<QIcon
v-if="row.isTaxDataChecked === 0"
name="vn:no036"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
</QIcon>
<QIcon v-if="row.hasTicketRequest" name="vn:buyrequest" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
</QIcon>
<QIcon v-if="row.itemShortage" name="vn:unavailable" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
</QIcon>
<QIcon v-if="row.isFreezed" name="vn:frozen" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.risk"
name="vn:risk"
:color="row.hasHighRisk ? 'negative' : 'primary'"
size="xs"
>
<QTooltip>
{{ $t('salesTicketsTable.risk') }}: {{ row.risk - row.credit }}
</QTooltip>
</QIcon>
<QIcon v-if="row.hasComponentLack" name="vn:components" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip>
</QIcon>
<QIcon v-if="row.isTooLittle" name="vn:isTooLittle" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
</QIcon>
</span>
</template>

View File

@ -143,6 +143,10 @@ function alignRow() {
const showFilter = computed( const showFilter = computed(
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions' () => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
); );
const onTabPressed = async () => {
if (model.value) enterEvent['keyup.enter']();
};
</script> </script>
<template> <template>
<div <div
@ -157,6 +161,7 @@ const showFilter = computed(
v-model="model" v-model="model"
:components="components" :components="components"
component-prop="columnFilter" component-prop="columnFilter"
@keydown.tab="onTabPressed"
/> />
</div> </div>
</template> </template>

View File

@ -162,9 +162,7 @@ onMounted(() => {
: $props.defaultMode; : $props.defaultMode;
stateStore.rightDrawer = quasar.screen.gt.xs; stateStore.rightDrawer = quasar.screen.gt.xs;
columnsVisibilitySkipped.value = [ columnsVisibilitySkipped.value = [
...splittedColumns.value.columns ...splittedColumns.value.columns.filter((c) => !c.visible).map((c) => c.name),
.filter((c) => c.visible == false)
.map((c) => c.name),
...['tableActions'], ...['tableActions'],
]; ];
createForm.value = $props.create; createForm.value = $props.create;
@ -237,7 +235,7 @@ function splitColumns(columns) {
if (col.create) splittedColumns.value.create.push(col); if (col.create) splittedColumns.value.create.push(col);
if (col.cardVisible) splittedColumns.value.cardVisible.push(col); if (col.cardVisible) splittedColumns.value.cardVisible.push(col);
if ($props.isEditable && col.disable == null) col.disable = false; if ($props.isEditable && col.disable == null) col.disable = false;
if ($props.useModel && col.columnFilter != false) if ($props.useModel && col.columnFilter !== false)
col.columnFilter = { inWhere: true, ...col.columnFilter }; col.columnFilter = { inWhere: true, ...col.columnFilter };
splittedColumns.value.columns.push(col); splittedColumns.value.columns.push(col);
} }
@ -326,6 +324,8 @@ function handleOnDataSaved(_) {
} }
function handleScroll() { function handleScroll() {
if ($props.crudModel.disableInfiniteScroll) return;
const tMiddle = tableRef.value.$el.querySelector('.q-table__middle'); const tMiddle = tableRef.value.$el.querySelector('.q-table__middle');
const { scrollHeight, scrollTop, clientHeight } = tMiddle; const { scrollHeight, scrollTop, clientHeight } = tMiddle;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40; const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40;
@ -394,7 +394,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
:name="col.orderBy ?? col.name" :name="col.orderBy ?? col.name"
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
:search-url="searchUrl" :search-url="searchUrl"
:vertical="true" :vertical="false"
/> />
</div> </div>
<slot <slot

View File

@ -101,7 +101,13 @@ const mixinRules = [
<QIcon <QIcon
name="close" name="close"
size="xs" size="xs"
v-if="hover && value && !$attrs.disabled && $props.clearable" v-if="
hover &&
value &&
!$attrs.disabled &&
!$attrs.readonly &&
$props.clearable
"
@click=" @click="
() => { () => {
value = null; value = null;

View File

@ -1,8 +1,7 @@
<script setup> <script setup>
import { onMounted, watch, computed, ref } from 'vue'; import { onMounted, watch, computed, ref, useAttrs } from 'vue';
import { date } from 'quasar'; import { date } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAttrs } from 'vue';
import VnDate from './VnDate.vue'; import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired'; import { useRequired } from 'src/composables/useRequired';

View File

@ -2,5 +2,12 @@
const model = defineModel({ type: Boolean, required: true }); const model = defineModel({ type: Boolean, required: true });
</script> </script>
<template> <template>
<QRadio v-model="model" v-bind="$attrs" dense :dark="true" class="q-mr-sm" /> <QRadio
v-model="model"
v-bind="$attrs"
dense
:dark="true"
class="q-mr-sm"
size="xs"
/>
</template> </template>

View File

@ -138,8 +138,6 @@ onMounted(() => {
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300); if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
}); });
defineExpose({ opts: myOptions });
const arrayDataKey = const arrayDataKey =
$props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label); $props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label);
@ -202,7 +200,10 @@ async function fetchFilter(val) {
if (fields) fetchOptions.fields = fields; if (fields) fetchOptions.fields = fields;
if (sortBy) fetchOptions.order = sortBy; if (sortBy) fetchOptions.order = sortBy;
arrayData.reset(['skip', 'filter.skip', 'page']); arrayData.reset(['skip', 'filter.skip', 'page']);
return (await arrayData.applyFilter({ filter: fetchOptions }))?.data;
const { data } = await arrayData.applyFilter({ filter: fetchOptions });
setOptions(data);
return data;
} }
async function filterHandler(val, update) { async function filterHandler(val, update) {
@ -256,6 +257,30 @@ async function onScroll({ to, direction, from, index }) {
isLoading.value = false; isLoading.value = false;
} }
} }
defineExpose({ opts: myOptions });
function handleKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
const inputValue = vnSelectRef.value?.inputValue;
if (inputValue) {
const matchingOption = myOptions.value.find(
(option) =>
option[optionLabel.value].toLowerCase() === inputValue.toLowerCase()
);
if (matchingOption) {
emit('update:modelValue', matchingOption[optionValue.value]);
} else {
emit('update:modelValue', inputValue);
}
vnSelectRef.value?.hidePopup();
}
}
}
</script> </script>
<template> <template>
@ -266,6 +291,7 @@ async function onScroll({ to, direction, from, index }) {
:option-value="optionValue" :option-value="optionValue"
v-bind="$attrs" v-bind="$attrs"
@filter="filterHandler" @filter="filterHandler"
@keydown="handleKeyDown"
:emit-value="nullishToTrue($attrs['emit-value'])" :emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])" :map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])" :use-input="nullishToTrue($attrs['use-input'])"

View File

@ -37,7 +37,7 @@ const $props = defineProps({
}, },
hiddenTags: { hiddenTags: {
type: Array, type: Array,
default: () => ['filter', 'search', 'or', 'and'], default: () => ['filter', 'or', 'and'],
}, },
customTags: { customTags: {
type: Array, type: Array,
@ -61,7 +61,6 @@ const emit = defineEmits([
'update:modelValue', 'update:modelValue',
'refresh', 'refresh',
'clear', 'clear',
'search',
'init', 'init',
'remove', 'remove',
'setUserParams', 'setUserParams',

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="vn-row q-gutter-md q-mb-md"> <div class="vn-row q-gutter-md">
<slot /> <slot />
</div> </div>
</template> </template>
@ -18,6 +18,9 @@
&:not(.wrap) { &:not(.wrap) {
flex-direction: column; flex-direction: column;
} }
&[fixed] {
flex-direction: row;
}
} }
} }
</style> </style>

View File

@ -75,18 +75,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
limit: store.limit, limit: store.limit,
}; };
let exprFilter;
let userParams = { ...store.userParams }; let userParams = { ...store.userParams };
if (store?.exprBuilder) {
const where = buildFilter(userParams, (param, value) => {
const res = store.exprBuilder(param, value);
if (res) delete userParams[param];
return res;
});
exprFilter = where ? { where } : null;
}
Object.assign(filter, store.userFilter, exprFilter); Object.assign(filter, store.userFilter);
let where; let where;
if (filter?.where || store.filter?.where) if (filter?.where || store.filter?.where)
where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {}); where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
@ -96,11 +88,28 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
Object.assign(params, userParams); Object.assign(params, userParams);
params.filter.skip = store.skip; params.filter.skip = store.skip;
if (store.order && store.order.length) params.filter.order = store.order; if (store?.order && typeof store?.order == 'string') store.order = [store.order];
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order; else delete params.filter.order;
store.currentFilter = JSON.parse(JSON.stringify(params));
delete store.currentFilter.filter.include;
store.currentFilter.filter = JSON.stringify(store.currentFilter.filter);
let exprFilter;
if (store?.exprBuilder) {
exprFilter = buildFilter(params, (param, value) => {
if (param == 'filter') return;
const res = store.exprBuilder(param, value);
if (res) delete params[param];
return res;
});
}
if (params.filter.where || exprFilter)
params.filter.where = { ...params.filter.where, ...exprFilter };
params.filter = JSON.stringify(params.filter); params.filter = JSON.stringify(params.filter);
store.currentFilter = params;
store.isLoading = true; store.isLoading = true;
const response = await axios.get(store.url, { const response = await axios.get(store.url, {
signal: canceller.signal, signal: canceller.signal,

View File

@ -241,7 +241,7 @@ input::-webkit-inner-spin-button {
th, th,
td { td {
padding: 1px 10px 1px 10px; padding: 1px 10px 1px 10px;
max-width: 100px; max-width: 130px;
div span { div span {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;

View File

@ -506,6 +506,7 @@ invoiceOut:
invoiceWithFutureDate: Exists an invoice with a future date invoiceWithFutureDate: Exists an invoice with a future date
noTicketsToInvoice: There are not tickets to invoice noTicketsToInvoice: There are not tickets to invoice
criticalInvoiceError: 'Critical invoicing error, process stopped' criticalInvoiceError: 'Critical invoicing error, process stopped'
invalidSerialTypeForAll: The serial type must be global when invoicing all clients
table: table:
addressId: Address id addressId: Address id
streetAddress: Street streetAddress: Street
@ -583,15 +584,15 @@ worker:
role: Role role: Role
sipExtension: Extension sipExtension: Extension
locker: Locker locker: Locker
fiDueDate: Fecha de caducidad del DNI fiDueDate: FI due date
sex: Sexo sex: Sex
seniority: Antigüedad seniority: Seniority
fi: DNI/NIE/NIF fi: DNI/NIE/NIF
birth: Fecha de nacimiento birth: Birth
isFreelance: Autónomo isFreelance: Freelance
isSsDiscounted: Bonificación SS isSsDiscounted: Bonificación SS
hasMachineryAuthorized: Autorizado para llevar maquinaria hasMachineryAuthorized: Machinery authorized
isDisable: Trabajador desactivado isDisable: Disable
notificationsManager: notificationsManager:
activeNotifications: Active notifications activeNotifications: Active notifications
availableNotifications: Available notifications availableNotifications: Available notifications
@ -707,7 +708,7 @@ supplier:
supplierName: Supplier name supplierName: Supplier name
basicData: basicData:
workerFk: Responsible workerFk: Responsible
isSerious: Verified isReal: Verified
isActive: Active isActive: Active
isPayMethodChecked: PayMethod checked isPayMethodChecked: PayMethod checked
note: Notes note: Notes
@ -857,6 +858,7 @@ components:
downloadFile: Download file downloadFile: Download file
openCard: View openCard: View
openSummary: Summary openSummary: Summary
viewSummary: Summary
cardDescriptor: cardDescriptor:
mainList: Main list mainList: Main list
summary: Summary summary: Summary

View File

@ -509,6 +509,7 @@ invoiceOut:
invoiceWithFutureDate: Existe una factura con una fecha futura invoiceWithFutureDate: Existe una factura con una fecha futura
noTicketsToInvoice: No existen tickets para facturar noTicketsToInvoice: No existen tickets para facturar
criticalInvoiceError: Error crítico en la facturación proceso detenido criticalInvoiceError: Error crítico en la facturación proceso detenido
invalidSerialTypeForAll: El tipo de serie debe ser global cuando se facturan todos los clientes
table: table:
addressId: Id dirección addressId: Id dirección
streetAddress: Dirección fiscal streetAddress: Dirección fiscal
@ -579,9 +580,9 @@ worker:
newWorker: Nuevo trabajador newWorker: Nuevo trabajador
summary: summary:
boss: Jefe boss: Jefe
phoneExtension: Extensión de teléfono phoneExtension: Ext. de teléfono
entPhone: Teléfono de empresa entPhone: Tel. de empresa
personalPhone: Teléfono personal personalPhone: Tel. personal
noBoss: Sin jefe noBoss: Sin jefe
userData: Datos de usuario userData: Datos de usuario
userId: ID del usuario userId: ID del usuario
@ -702,7 +703,7 @@ supplier:
supplierName: Nombre del proveedor supplierName: Nombre del proveedor
basicData: basicData:
workerFk: Responsable workerFk: Responsable
isSerious: Verificado isReal: Verificado
isActive: Activo isActive: Activo
isPayMethodChecked: Método de pago validado isPayMethodChecked: Método de pago validado
note: Notas note: Notas

View File

@ -130,7 +130,7 @@ function cancel() {
<template #body-cell-description="{ row, value }"> <template #body-cell-description="{ row, value }">
<QTd auto-width align="right" class="link"> <QTd auto-width align="right" class="link">
{{ value }} {{ value }}
<ItemDescriptorProxy :id="row.itemFk"></ItemDescriptorProxy> <ItemDescriptorProxy :id="row.itemFk" />
</QTd> </QTd>
</template> </template>
</QTable> </QTable>

View File

@ -25,7 +25,7 @@ const claimFilter = computed(() => {
include: { include: {
relation: 'user', relation: 'user',
scope: { scope: {
fields: ['id', 'nickname'], fields: ['id', 'nickname', 'name'],
}, },
}, },
}, },

View File

@ -256,10 +256,10 @@ const showBalancePdf = ({ id }) => {
{{ toCurrency(balances[rowIndex]?.balance) }} {{ toCurrency(balances[rowIndex]?.balance) }}
</template> </template>
<template #column-description="{ row }"> <template #column-description="{ row }">
<div class="link" v-if="row.isInvoice"> <span class="link" v-if="row.isInvoice" @click.stop>
{{ t('bill', { ref: row.description }) }} {{ t('bill', { ref: row.description }) }}
<InvoiceOutDescriptorProxy :id="row.description" /> <InvoiceOutDescriptorProxy :id="row.id" />
</div> </span>
<span v-else class="q-pa-xs dotted rounded-borders" :title="row.description"> <span v-else class="q-pa-xs dotted rounded-borders" :title="row.description">
{{ row.description }} {{ row.description }}
</span> </span>

View File

@ -320,7 +320,7 @@ const sumRisk = ({ clientRisks }) => {
:value="entity.recommendedCredit" :value="entity.recommendedCredit"
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-max">
<VnTitle :text="t('Latest tickets')" /> <VnTitle :text="t('Latest tickets')" />
<CustomerSummaryTable /> <CustomerSummaryTable />
</QCard> </QCard>

View File

@ -12,6 +12,7 @@ import VnImg from 'src/components/ui/VnImg.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref();
const columns = [ const columns = [
{ {
align: 'center', align: 'center',
@ -234,7 +235,6 @@ const columns = [
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)), format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)),
}, },
]; ];
const tableRef = ref();
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;

View File

@ -183,7 +183,7 @@ onMounted(async () => {
<i18n> <i18n>
en: en:
invoiceDate: Invoice date invoiceDate: Invoice date
maxShipped: Max date maxShipped: Max date ticket
allClients: All clients allClients: All clients
oneClient: One client oneClient: One client
company: Company company: Company
@ -195,7 +195,7 @@ en:
es: es:
invoiceDate: Fecha de factura invoiceDate: Fecha de factura
maxShipped: Fecha límite maxShipped: Fecha límite ticket
allClients: Todos los clientes allClients: Todos los clientes
oneClient: Un solo cliente oneClient: Un solo cliente
company: Empresa company: Empresa

View File

@ -6,15 +6,19 @@ import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue'; import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import { toCurrency, toDate } from 'src/filters/index'; import { toCurrency, toDate } from 'src/filters/index';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue'; import InvoiceOutFilter from './InvoiceOutFilter.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnRadio from 'src/components/common/VnRadio.vue';
import VnInput from 'src/components/common/VnInput.vue';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -26,99 +30,86 @@ const selectedRows = ref([]);
const hasSelectedCards = computed(() => selectedRows.value.length > 0); const hasSelectedCards = computed(() => selectedRows.value.length > 0);
const MODEL = 'InvoiceOuts'; const MODEL = 'InvoiceOuts';
const { openReport } = usePrintService(); const { openReport } = usePrintService();
const addressOptions = ref([]);
const selectedOption = ref('ticket');
async function fetchClientAddress(id) {
const { data } = await axios.get(
`Clients/${id}/addresses?filter[order]=isActive DESC`
);
addressOptions.value = data;
}
const exprBuilder = (_, value) => {
return {
or: [{ code: value }, { description: { like: `%${value}%` } }],
};
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'center', align: 'center',
name: 'id', name: 'id',
label: t('invoiceOutList.tableVisibleColumns.id'), label: t('invoiceOutList.tableVisibleColumns.id'),
chip: { chip: { condition: () => true },
condition: () => true,
},
isId: true, isId: true,
columnFilter: { columnFilter: { name: 'search' },
name: 'search',
},
}, },
{ {
align: 'left', align: 'left',
name: 'ref', name: 'ref',
label: t('invoiceOutList.tableVisibleColumns.ref'), label: t('globals.reference'),
isTitle: true, isTitle: true,
component: 'select', component: 'select',
attrs: { attrs: { url: MODEL, optionLabel: 'ref', optionValue: 'id' },
url: MODEL, columnField: { component: null },
optionLabel: 'ref',
optionValue: 'id',
},
columnField: {
component: null,
},
}, },
{ {
align: 'left', align: 'left',
name: 'Issued', name: 'issued',
label: t('invoiceOutList.tableVisibleColumns.issued'), label: t('invoiceOut.summary.issued'),
component: 'date', component: 'date',
format: (row) => toDate(row.issued), format: (row) => toDate(row.issued),
columnField: { columnField: { component: null },
component: null,
},
}, },
{ {
align: 'left', align: 'left',
name: 'clientFk', name: 'clientFk',
label: t('invoiceOutModule.customer'), label: t('globals.client'),
cardVisible: true, cardVisible: true,
component: 'select', component: 'select',
attrs: { attrs: { url: 'Clients', fields: ['id', 'name'] },
url: 'Clients', columnField: { component: null },
fields: ['id', 'name'],
},
columnField: {
component: null,
},
}, },
{ {
align: 'left', align: 'left',
name: 'companyCode', name: 'companyCode',
label: t('invoiceOutModule.company'), label: t('globals.company'),
cardVisible: true, cardVisible: true,
component: 'select', component: 'select',
attrs: { attrs: { url: 'Companies', optionLabel: 'code', optionValue: 'id' },
url: 'Companies', columnField: { component: null },
optionLabel: 'code',
optionValue: 'id',
},
columnField: {
component: null,
},
}, },
{ {
align: 'left', align: 'left',
name: 'amount', name: 'amount',
label: t('invoiceOutModule.amount'), label: t('globals.amount'),
cardVisible: true, cardVisible: true,
format: (row) => toCurrency(row.amount), format: (row) => toCurrency(row.amount),
}, },
{ {
align: 'left', align: 'left',
name: 'created', name: 'created',
label: t('invoiceOutList.tableVisibleColumns.created'), label: t('globals.created'),
component: 'date', component: 'date',
columnField: { columnField: { component: null },
component: null,
},
format: (row) => toDate(row.created), format: (row) => toDate(row.created),
}, },
{ {
align: 'left', align: 'left',
name: 'dued', name: 'dued',
label: t('invoiceOutList.tableVisibleColumns.dueDate'), label: t('invoiceOut.summary.dued'),
component: 'date', component: 'date',
columnField: { columnField: { component: null },
component: null,
},
format: (row) => toDate(row.dued), format: (row) => toDate(row.dued),
}, },
{ {
@ -128,11 +119,12 @@ const columns = computed(() => [
{ {
title: t('components.smartCard.viewSummary'), title: t('components.smartCard.viewSummary'),
icon: 'preview', icon: 'preview',
isPrimary: true,
action: (row) => viewSummary(row.id, InvoiceOutSummary), action: (row) => viewSummary(row.id, InvoiceOutSummary),
}, },
{ {
title: t('DownloadPdf'), title: t('globals.downloadPdf'),
icon: 'vn:ticket', icon: 'cloud_download',
isPrimary: true, isPrimary: true,
action: (row) => openPdf(row.id), action: (row) => openPdf(row.id),
}, },
@ -181,7 +173,7 @@ watchEffect(selectedRows);
<template> <template>
<VnSearchbar <VnSearchbar
:info="t('youCanSearchByInvoiceReference')" :info="t('youCanSearchByInvoiceReference')"
:label="t('searchInvoice')" :label="t('Search invoice')"
data-key="invoiceOutList" data-key="invoiceOutList"
/> />
<RightMenu> <RightMenu>
@ -197,7 +189,7 @@ watchEffect(selectedRows);
@click="downloadPdf()" @click="downloadPdf()"
:disable="!hasSelectedCards" :disable="!hasSelectedCards"
> >
<QTooltip>{{ t('globals.downloadPdf') }}</QTooltip> <QTooltip>{{ t('downloadPdf') }}</QTooltip>
</QBtn> </QBtn>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
@ -207,11 +199,9 @@ watchEffect(selectedRows);
:url="`${MODEL}/filter`" :url="`${MODEL}/filter`"
:create="{ :create="{
urlCreate: 'InvoiceOuts/createManualInvoice', urlCreate: 'InvoiceOuts/createManualInvoice',
title: t('Create manual invoice'), title: t('createManualInvoice'),
onDataSaved: ({ id }) => tableRef.redirect(id), onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: { formInitialData: { active: true },
active: true,
},
}" }"
:right-search="false" :right-search="false"
v-model:selected="selectedRows" v-model:selected="selectedRows"
@ -231,74 +221,199 @@ watchEffect(selectedRows);
</span> </span>
</template> </template>
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<div class="flex no-wrap flex-center"> <div class="row q-col-gutter-xs">
<VnSelect <div class="col-12">
url="Tickets" <div class="q-col-gutter-xs">
v-model="data.ticketFk" <VnRow fixed>
:label="t('invoiceOutList.tableVisibleColumns.ticket')" <VnRadio
option-label="id" v-model="selectedOption"
option-value="id" val="ticket"
> :label="t('globals.ticket')"
<template #option="scope"> class="q-my-none q-mr-md"
<QItem v-bind="scope.itemProps"> />
<QItemSection>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel> <VnInput
<QItemLabel caption>{{ scope.opt?.nickname }}</QItemLabel> v-show="selectedOption === 'ticket'"
</QItemSection> v-model="data.ticketFk"
</QItem> :label="t('globals.ticket')"
</template> style="flex: 1"
</VnSelect> />
<span class="q-ml-md">O</span>
<div
class="row q-col-gutter-xs q-ml-none"
v-show="selectedOption !== 'ticket'"
>
<div class="col">
<VnSelect
v-model="data.clientFk"
:label="t('globals.client')"
url="Clients"
:options="customerOptions"
option-label="name"
option-value="id"
@update:model-value="fetchClientAddress"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
#{{ scope.opt?.id }} -
{{ scope.opt?.name }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
<div class="col">
<VnSelect
v-model="data.addressFk"
:label="t('ticket.summary.consignee')"
:options="addressOptions"
option-label="nickname"
option-value="id"
v-if="
data.clientFk &&
selectedOption === 'consignatario'
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
:class="{
'color-vn-label':
!scope.opt?.isActive,
}"
>
{{
`${
!scope.opt?.isActive
? t('inactive')
: ''
} `
}}
<span>{{
scope.opt?.nickname
}}</span>
<span
v-if="
scope.opt?.province ||
scope.opt?.city ||
scope.opt?.street
"
>
, {{ scope.opt?.street }},
{{ scope.opt?.city }},
{{
scope.opt?.province?.name
}}
-
{{
scope.opt?.agencyMode
?.name
}}
</span>
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</div>
</VnRow>
<VnRow fixed>
<VnRadio
v-model="selectedOption"
val="cliente"
:label="t('globals.client')"
class="q-my-none q-mr-md"
/>
</VnRow>
<VnRow fixed>
<VnRadio
v-model="selectedOption"
val="consignatario"
:label="t('ticket.summary.consignee')"
class="q-my-none q-mr-md"
/>
</VnRow>
</div>
</div>
<div class="full-width">
<VnRow class="row q-col-gutter-xs">
<VnSelect
url="InvoiceOutSerials"
v-model="data.serial"
:label="t('invoiceIn.serial')"
:options="invoiceOutSerialsOptions"
option-label="description"
option-value="code"
option-filter
:expr-builder="exprBuilder"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.code }} -
{{ scope.opt?.description }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInputDate
:label="t('invoiceOut.summary.dued')"
v-model="data.maxShipped"
/>
</VnRow>
<VnRow class="row q-col-gutter-xs">
<VnSelect
url="TaxAreas"
v-model="data.taxArea"
:label="t('invoiceOutList.tableVisibleColumns.taxArea')"
:options="taxAreasOptions"
option-label="code"
option-value="code"
/>
<VnInput
v-model="data.reference"
:label="t('globals.reference')"
/>
</VnRow>
</div>
</div> </div>
<VnSelect
url="Clients"
v-model="data.clientFk"
:label="t('invoiceOutModule.customer')"
:options="customerOptions"
option-label="name"
option-value="id"
/>
<VnSelect
url="InvoiceOutSerials"
v-model="data.serial"
:label="t('invoiceOutList.tableVisibleColumns.invoiceOutSerial')"
:options="invoiceOutSerialsOptions"
option-label="description"
option-value="code"
/>
<VnInputDate
:label="t('invoiceOutList.tableVisibleColumns.dueDate')"
v-model="data.maxShipped"
/>
<VnSelect
url="TaxAreas"
v-model="data.taxArea"
:label="t('invoiceOutList.tableVisibleColumns.taxArea')"
:options="taxAreasOptions"
option-label="code"
option-value="code"
/>
<QInput
v-model="data.reference"
:label="t('invoiceOutList.tableVisibleColumns.ref')"
/>
</template> </template>
</VnTable> </VnTable>
</template> </template>
<style lang="scss" scoped>
#formModel .vn-row {
min-height: 45px;
.q-radio {
align-self: flex-end;
flex: 0.3;
}
> .q-input,
> .q-select {
flex: 0.75;
}
}
</style>
<i18n> <i18n>
en: en:
searchInvoice: Search issued invoice invoiceId: Invoice ID
fileDenied: Browser denied file download... youCanSearchByInvoiceReference: You can search by invoice reference
fileAllowed: Successful download of CSV file createManualInvoice: Create Manual Invoice
youCanSearchByInvoiceReference: You can search by invoice reference inactive: (Inactive)
createInvoice: Make invoice
Create manual invoice: Create manual invoice es:
es: invoiceId: ID de factura
searchInvoice: Buscar factura emitida youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura
fileDenied: El navegador denegó la descarga de archivos... createManualInvoice: Crear factura manual
fileAllowed: Descarga exitosa de archivo CSV inactive: (Inactivo)
youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura
createInvoice: Crear factura
Create manual invoice: Crear factura manual
</i18n> </i18n>

View File

@ -2,6 +2,7 @@ invoiceOutModule:
customer: Client customer: Client
amount: Amount amount: Amount
company: Company company: Company
address: Address
invoiceOutList: invoiceOutList:
tableVisibleColumns: tableVisibleColumns:
id: ID id: ID
@ -15,11 +16,11 @@ invoiceOutList:
DownloadPdf: Download PDF DownloadPdf: Download PDF
InvoiceOutSummary: Summary InvoiceOutSummary: Summary
negativeBases: negativeBases:
country: Country country: Country
clientId: Client ID clientId: Client ID
base: Base base: Base
ticketId: Ticket ticketId: Ticket
active: Active active: Active
hasToInvoice: Has to invoice hasToInvoice: Has to invoice
verifiedData: Verified data verifiedData: Verified data
commercial: Commercial commercial: Commercial

View File

@ -4,6 +4,7 @@ invoiceOutModule:
customer: Cliente customer: Cliente
amount: Importe amount: Importe
company: Empresa company: Empresa
address: Consignatario
invoiceOutList: invoiceOutList:
tableVisibleColumns: tableVisibleColumns:
id: ID id: ID

View File

@ -440,7 +440,7 @@ function handleOnDataSave({ CrudModelRef }) {
selection: 'multiple', selection: 'multiple',
}" }"
:crud-model="{ :crud-model="{
paginate: false, disableInfiniteScroll: true,
}" }"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:row-click="saveOnRowChange" :row-click="saveOnRowChange"

View File

@ -27,7 +27,7 @@ function exprBuilder(param, value) {
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('salesOrdersTable.dateSend'), label: t('globals.landed'),
name: 'dateSend', name: 'dateSend',
field: 'dateSend', field: 'dateSend',
align: 'left', align: 'left',

View File

@ -15,6 +15,7 @@ import { toCurrency, dateRange, dashIfEmpty } from 'src/filters';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import MonitorTicketSearchbar from './MonitorTicketSearchbar.vue'; import MonitorTicketSearchbar from './MonitorTicketSearchbar.vue';
import MonitorTicketFilter from './MonitorTicketFilter.vue'; import MonitorTicketFilter from './MonitorTicketFilter.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
const DEFAULT_AUTO_REFRESH = 2 * 60 * 1000; // 2min in ms const DEFAULT_AUTO_REFRESH = 2 * 60 * 1000; // 2min in ms
const { t } = useI18n(); const { t } = useI18n();
@ -23,13 +24,18 @@ const tableRef = ref(null);
const provinceOpts = ref([]); const provinceOpts = ref([]);
const stateOpts = ref([]); const stateOpts = ref([]);
const zoneOpts = ref([]); const zoneOpts = ref([]);
const visibleColumns = ref([]);
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const from = Date.vnNew(); const from = Date.vnNew();
from.setHours(0, 0, 0, 0); from.setHours(0, 0, 0, 0);
const to = new Date(from.getTime()); const to = new Date(from.getTime());
to.setDate(to.getDate() + 1); to.setDate(to.getDate() + 1);
to.setHours(23, 59, 59, 999); to.setHours(23, 59, 59, 999);
const stateColors = {
notice: 'info',
success: 'positive',
warning: 'warning',
alert: 'negative',
};
function exprBuilder(param, value) { function exprBuilder(param, value) {
switch (param) { switch (param) {
@ -224,7 +230,7 @@ const columns = computed(() => [
{ {
title: t('salesTicketsTable.goToLines'), title: t('salesTicketsTable.goToLines'),
icon: 'vn:lines', icon: 'vn:lines',
color: 'priamry', color: 'primary',
action: (row) => openTab(row.id), action: (row) => openTab(row.id),
isPrimary: true, isPrimary: true,
attrs: { attrs: {
@ -235,7 +241,7 @@ const columns = computed(() => [
{ {
title: t('salesTicketsTable.preview'), title: t('salesTicketsTable.preview'),
icon: 'preview', icon: 'preview',
color: 'priamry', color: 'primary',
action: (row) => viewSummary(row.id, TicketSummary), action: (row) => viewSummary(row.id, TicketSummary),
isPrimary: true, isPrimary: true,
attrs: { attrs: {
@ -253,10 +259,10 @@ const getBadgeAttrs = (date) => {
let timeTicket = new Date(date); let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0); timeTicket.setHours(0, 0, 0, 0);
let comparation = today - timeTicket; let timeDiff = today - timeTicket;
if (comparation == 0) return { color: 'warning', 'text-color': 'black' }; if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' };
if (comparation < 0) return { color: 'success', 'text-color': 'black' }; if (timeDiff < 0) return { color: 'success', 'text-color': 'black' };
return { color: 'transparent', 'text-color': 'white' }; return { color: 'transparent', 'text-color': 'white' };
}; };
@ -271,13 +277,6 @@ const autoRefreshHandler = (value) => {
} }
}; };
const stateColors = {
notice: 'info',
success: 'positive',
warning: 'warning',
alert: 'negative',
};
const totalPriceColor = (ticket) => { const totalPriceColor = (ticket) => {
const total = parseInt(ticket.totalWithVat); const total = parseInt(ticket.totalWithVat);
if (total > 0 && total < 50) return 'warning'; if (total > 0 && total < 50) return 'warning';
@ -285,10 +284,10 @@ const totalPriceColor = (ticket) => {
const formatShippedDate = (date) => { const formatShippedDate = (date) => {
if (!date) return '-'; if (!date) return '-';
const split1 = date.split('T'); const dateSplit = date.split('T');
const [year, month, day] = split1[0].split('-'); const [year, month, day] = dateSplit[0].split('-');
const _date = new Date(year, month - 1, day); const newDate = new Date(year, month - 1, day);
return toDateFormat(_date); return toDateFormat(newDate);
}; };
const openTab = (id) => const openTab = (id) =>
@ -336,7 +335,6 @@ const openTab = (id) =>
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:offset="50" :offset="50"
:columns="columns" :columns="columns"
:visible-columns="visibleColumns"
:right-search="false" :right-search="false"
default-mode="table" default-mode="table"
auto-load auto-load
@ -366,61 +364,7 @@ const openTab = (id) =>
</QCheckbox> </QCheckbox>
</template> </template>
<template #column-totalProblems="{ row }"> <template #column-totalProblems="{ row }">
<span> <TicketProblems :row="row" />
<QIcon
v-if="row.isTaxDataChecked === 0"
name="vn:no036"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.noVerifiedData') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.hasTicketRequest"
name="vn:buyrequest"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.itemShortage"
name="vn:unavailable"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.notVisible') }}</QTooltip>
</QIcon>
<QIcon v-if="row.isFreezed" name="vn:frozen" color="primary" size="xs">
<QTooltip>{{ $t('salesTicketsTable.clientFrozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.risk"
name="vn:risk"
:color="row.hasHighRisk ? 'negative' : 'primary'"
size="xs"
>
<QTooltip
>{{ $t('salesTicketsTable.risk') }}: {{ row.risk }}</QTooltip
>
</QIcon>
<QIcon
v-if="row.hasComponentLack"
name="vn:components"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.componentLack') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.isTooLittle"
name="vn:isTooLittle"
color="primary"
size="xs"
>
<QTooltip>{{ $t('salesTicketsTable.tooLittle') }}</QTooltip>
</QIcon>
</span>
</template> </template>
<template #column-id="{ row }"> <template #column-id="{ row }">
<span class="link" @click.stop.prevent> <span class="link" @click.stop.prevent>
@ -475,7 +419,7 @@ const openTab = (id) =>
</QIcon> </QIcon>
</template> </template>
<template #column-zoneFk="{ row }"> <template #column-zoneFk="{ row }">
<div @click.stop.prevent :title="row.zoneName"> <div v-if="row.zoneFk" @click.stop.prevent :title="row.zoneName">
<span class="link">{{ row.zoneName }}</span> <span class="link">{{ row.zoneName }}</span>
<ZoneDescriptorProxy :id="row.zoneFk" /> <ZoneDescriptorProxy :id="row.zoneFk" />
</div> </div>

View File

@ -11,7 +11,6 @@ salesClientsTable:
client: Client client: Client
salesOrdersTable: salesOrdersTable:
delete: Delete delete: Delete
dateSend: Send date
dateMake: Make date dateMake: Make date
deleteConfirmMessage: All the selected elements will be deleted. Are you sure you want to continue? deleteConfirmMessage: All the selected elements will be deleted. Are you sure you want to continue?
agency: Agency agency: Agency

View File

@ -11,7 +11,6 @@ salesClientsTable:
client: Cliente client: Cliente
salesOrdersTable: salesOrdersTable:
delete: Eliminar delete: Eliminar
dateSend: Fecha de envío
dateMake: Fecha de realización dateMake: Fecha de realización
deleteConfirmMessage: Todos los elementos seleccionados serán eliminados. ¿Seguro que quieres continuar? deleteConfirmMessage: Todos los elementos seleccionados serán eliminados. ¿Seguro que quieres continuar?
agency: Agencia agency: Agencia

View File

@ -166,6 +166,7 @@ onMounted(() => {
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:custom-tags="['tagGroups', 'categoryFk']" :custom-tags="['tagGroups', 'categoryFk']"
:redirect="false" :redirect="false"
search-url="params"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'typeFk'"> <strong v-if="tag.label === 'typeFk'">

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { onMounted, ref } from 'vue'; import { reactive, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { useState } from 'composables/useState'; import { useState } from 'composables/useState';
@ -9,7 +9,6 @@ import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import { useDialogPluginComponent } from 'quasar'; import { useDialogPluginComponent } from 'quasar';
import { reactive } from 'vue';
const { t } = useI18n(); const { t } = useI18n();
const state = useState(); const state = useState();
@ -58,10 +57,6 @@ const fetchAgencyList = async (landed, addressFk) => {
} }
}; };
// const fetchOrderDetails = (order) => {
// fetchAddressList(order?.addressFk);
// fetchAgencyList(order?.landed, order?.addressFk);
// };
const $props = defineProps({ const $props = defineProps({
clientFk: { clientFk: {
type: Number, type: Number,
@ -73,39 +68,6 @@ const initialFormState = reactive({
addressId: null, addressId: null,
clientFk: $props.clientFk, clientFk: $props.clientFk,
}); });
// const orderMapper = (order) => {
// return {
// addressId: order.addressFk,
// agencyModeId: order.agencyModeFk,
// landed: new Date(order.landed).toISOString(),
// };
// };
// const orderFilter = {
// include: [
// { relation: 'agencyMode', scope: { fields: ['name'] } },
// {
// relation: 'address',
// scope: { fields: ['nickname'] },
// },
// { relation: 'rows', scope: { fields: ['id'] } },
// {
// relation: 'client',
// scope: {
// fields: [
// 'salesPersonFk',
// 'name',
// 'isActive',
// 'isFreezed',
// 'isTaxDataChecked',
// ],
// include: {
// relation: 'salesPersonUser',
// scope: { fields: ['id', 'name'] },
// },
// },
// },
// ],
// };
const onClientChange = async (clientId = $props.clientFk) => { const onClientChange = async (clientId = $props.clientFk) => {
try { try {

View File

@ -111,6 +111,7 @@ const columns = computed(() => [
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Items', url: 'Items',
sortBy: 'name ASC ',
fields: ['id', 'name', 'subName'], fields: ['id', 'name', 'subName'],
}, },
columnField: { columnField: {

View File

@ -126,7 +126,6 @@ const setWireTransfer = async () => {
(_, requestResponse) => (_, requestResponse) =>
onBankEntityCreated(requestResponse, row) onBankEntityCreated(requestResponse, row)
" "
:show-entity-field="false"
/> />
</template> </template>
<template #option="scope"> <template #option="scope">

View File

@ -76,8 +76,8 @@ const companySizes = [
</VnRow> </VnRow>
<VnRow> <VnRow>
<QCheckbox <QCheckbox
v-model="data.isSerious" v-model="data.isReal"
:label="t('supplier.basicData.isSerious')" :label="t('supplier.basicData.isReal')"
/> />
<QCheckbox <QCheckbox
v-model="data.isActive" v-model="data.isActive"

View File

@ -38,7 +38,7 @@ const filter = {
'payDemFk', 'payDemFk',
'payDay', 'payDay',
'isActive', 'isActive',
'isSerious', 'isReal',
'isTrucker', 'isTrucker',
'account', 'account',
], ],
@ -137,7 +137,7 @@ const getEntryQueryParams = (supplier) => {
<QTooltip>{{ t('Inactive supplier') }}</QTooltip> <QTooltip>{{ t('Inactive supplier') }}</QTooltip>
</QIcon> </QIcon>
<QIcon <QIcon
v-if="!supplier.isSerious" v-if="!supplier.isReal"
name="vn:supplierfalse" name="vn:supplierfalse"
color="primary" color="primary"
size="xs" size="xs"

View File

@ -83,7 +83,7 @@ const getUrl = (section) => `#/supplier/${entityId.value}/${section}`;
</VnLv> </VnLv>
<QCheckbox <QCheckbox
:label="t('supplier.summary.verified')" :label="t('supplier.summary.verified')"
v-model="supplier.isSerious" v-model="supplier.isReal"
:disable="true" :disable="true"
/> />
<QCheckbox <QCheckbox

View File

@ -239,7 +239,7 @@ function makeInvoiceDialog() {
} }
async function makeInvoice() { async function makeInvoice() {
const params = { const params = {
ticketsIds: [parseInt(ticketId)], ticketsIds: [parseInt(ticketId.value)],
}; };
await axios.post(`Tickets/invoiceTicketsAndPdf`, params); await axios.post(`Tickets/invoiceTicketsAndPdf`, params);
@ -265,7 +265,7 @@ async function transferClient(client) {
async function addTurn(day) { async function addTurn(day) {
const params = { const params = {
ticketFk: parseInt(ticketId), ticketFk: parseInt(ticketId.value),
weekDay: day, weekDay: day,
agencyModeFk: ticket.value.agencyModeFk, agencyModeFk: ticket.value.agencyModeFk,
}; };
@ -280,7 +280,7 @@ async function addTurn(day) {
async function createRefund(withWarehouse) { async function createRefund(withWarehouse) {
const params = { const params = {
ticketsIds: [parseInt(ticketId)], ticketsIds: [parseInt(ticketId.value)],
withWarehouse: withWarehouse, withWarehouse: withWarehouse,
negative: true, negative: true,
}; };
@ -368,7 +368,7 @@ async function uploadDocuware(force) {
const { data } = await axios.post(`Docuwares/upload`, { const { data } = await axios.post(`Docuwares/upload`, {
fileCabinet: 'deliveryNote', fileCabinet: 'deliveryNote',
ticketIds: [parseInt(ticketId)], ticketIds: [parseInt(ticketId.value)],
}); });
if (data) notify({ message: t('PDF sent!'), type: 'positive' }); if (data) notify({ message: t('PDF sent!'), type: 'positive' });

View File

@ -94,25 +94,32 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
label: t('ticketList.id'), label: t('globals.id'),
name: 'itemFk', name: 'itemFk',
}, },
{ {
align: 'left', align: 'left',
label: t('basicData.quantity'), label: t('globals.quantity'),
name: 'quantity', name: 'quantity',
format: (row) => toCurrency(row.quantity), format: (row) => toCurrency(row.quantity),
}, },
{ {
align: 'left', align: 'left',
label: t('basicData.item'), label: t('globals.item'),
name: 'item', name: 'item',
format: (row) => row?.item?.name, format: (row) => row?.item?.name,
columnClass: 'expand', columnClass: 'expand',
}, },
{ {
align: 'left', align: 'left',
label: t('basicData.price'), label: t('globals.size'),
name: 'size',
format: (row) => row?.item?.size,
columnClass: 'expand',
},
{
align: 'left',
label: t('globals.price'),
name: 'price', name: 'price',
format: (row) => toCurrency(row.price), format: (row) => toCurrency(row.price),
}, },
@ -124,7 +131,7 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
label: t('ticketList.amount'), label: t('globals.amount'),
name: 'amount', name: 'amount',
format: (row) => parseInt(row.amount * row.quantity), format: (row) => parseInt(row.amount * row.quantity),
}, },
@ -685,7 +692,7 @@ watch(
}" }"
:create-as-dialog="false" :create-as-dialog="false"
:crud-model="{ :crud-model="{
paginate: false, disableInfiniteScroll: true,
}" }"
:default-remove="false" :default-remove="false"
:default-reset="false" :default-reset="false"

View File

@ -215,7 +215,7 @@ const requestComponentUpdate = async (ticket, isWithoutNegatives) => {
if (!newLanded) { if (!newLanded) {
notify(t('advanceTickets.noDeliveryZone'), 'negative'); notify(t('advanceTickets.noDeliveryZone'), 'negative');
return; throw new Error(t('advanceTickets.noDeliveryZone'));
} }
ticket.landed = newLanded.landed; ticket.landed = newLanded.landed;
@ -299,10 +299,10 @@ const splitTickets = async () => {
const { query, params } = await requestComponentUpdate(ticket, true); const { query, params } = await requestComponentUpdate(ticket, true);
await axios.post(query, params); await axios.post(query, params);
progressAdd(ticket.futureId); progressAdd(ticket.futureId);
} catch (error) { } catch (e) {
splitErrors.value.push({ splitErrors.value.push({
id: ticket.futureId, id: ticket.futureId,
reason: error.response?.data?.error?.message, reason: e.message || e.response?.data?.error?.message,
}); });
progressAdd(ticket.futureId); progressAdd(ticket.futureId);
} }

View File

@ -22,6 +22,7 @@ import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorP
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
import { toTimeFormat } from 'src/filters/date'; import { toTimeFormat } from 'src/filters/date';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
import TicketProblems from 'src/components/TicketProblems.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -492,66 +493,7 @@ function setReference(data) {
data-cy="ticketListTable" data-cy="ticketListTable"
> >
<template #column-statusIcons="{ row }"> <template #column-statusIcons="{ row }">
<div class="q-gutter-x-xs"> <TicketProblems :row="row" />
<QIcon
v-if="row.isTaxDataChecked === 0"
color="primary"
name="vn:no036"
size="xs"
>
<QTooltip>
{{ t('No verified data') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.hasTicketRequest"
color="primary"
name="vn:buyrequest"
size="xs"
>
<QTooltip>
{{ t('Purchase request') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.itemShortage"
color="primary"
name="vn:unavailable"
size="xs"
>
<QTooltip>
{{ t('Not visible') }}
</QTooltip>
</QIcon>
<QIcon v-if="row.isFreezed" color="primary" name="vn:frozen" size="xs">
<QTooltip>
{{ t('Client frozen') }}
</QTooltip>
</QIcon>
<QIcon v-if="row.risk" color="primary" name="vn:risk" size="xs">
<QTooltip> {{ t('Risk') }}: {{ row.risk }} </QTooltip>
</QIcon>
<QIcon
v-if="row.hasComponentLack"
color="primary"
name="vn:components"
size="xs"
>
<QTooltip>
{{ t('Component lack') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.hasRounding"
color="primary"
name="sync_problem"
size="xs"
>
<QTooltip>
{{ t('Rounding') }}
</QTooltip>
</QIcon>
</div>
</template> </template>
<template #column-salesPersonFk="{ row }"> <template #column-salesPersonFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>

View File

@ -138,6 +138,7 @@ ticketSale:
ok: Ok ok: Ok
more: Más more: Más
address: Consignatario address: Consignatario
size: Medida
ticketComponents: ticketComponents:
serie: Serie serie: Serie
components: Componentes components: Componentes

View File

@ -15,7 +15,7 @@ const filter = {
include: { include: {
relation: 'user', relation: 'user',
scope: { scope: {
fields: ['id', 'nickname'], fields: ['id', 'nickname', 'name'],
}, },
}, },
}, },

View File

@ -75,10 +75,10 @@ onBeforeMount(async () => {
<VnLinkPhone :phone-number="worker.phone" /> <VnLinkPhone :phone-number="worker.phone" />
</template> </template>
</VnLv> </VnLv>
<VnLv :value="worker.client?.phone"> <VnLv :value="advancedSummary?.client?.phone">
<template #label> <template #label>
{{ t('worker.summary.personalPhone') }} {{ t('worker.summary.personalPhone') }}
<VnLinkPhone :phone-number="worker.client?.phone" /> <VnLinkPhone :phone-number="advancedSummary?.client?.phone" />
</template> </template>
</VnLv> </VnLv>
</QCard> </QCard>
@ -86,12 +86,12 @@ onBeforeMount(async () => {
<VnTitle :url="basicDataUrl" :text="t('globals.summary.basicData')" /> <VnTitle :url="basicDataUrl" :text="t('globals.summary.basicData')" />
<VnLv <VnLv
:label="t('worker.summary.fiDueDate')" :label="t('worker.summary.fiDueDate')"
:value="toDate(worker.fiDueDate)" :value="toDate(advancedSummary.fiDueDate)"
/> />
<VnLv :label="t('worker.summary.sex')" :value="worker.sex" /> <VnLv :label="t('worker.summary.sex')" :value="worker.sex" />
<VnLv <VnLv
:label="t('worker.summary.seniority')" :label="t('worker.summary.seniority')"
:value="toDate(worker.seniority)" :value="toDate(advancedSummary.seniority)"
/> />
<VnLv :label="t('worker.summary.fi')" :value="advancedSummary.fi" /> <VnLv :label="t('worker.summary.fi')" :value="advancedSummary.fi" />
<VnLv <VnLv

View File

@ -306,7 +306,7 @@ async function autofillBic(worker) {
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput <VnInput
:label="t('worker.create.street')" :label="t('globals.street')"
:model-value="uppercaseStreetModel(data).get()" :model-value="uppercaseStreetModel(data).get()"
@update:model-value="uppercaseStreetModel(data).set" @update:model-value="uppercaseStreetModel(data).set"
:disable="data.isFreelance" :disable="data.isFreelance"

View File

@ -113,7 +113,7 @@ export default {
name: 'SupplierAccounts', name: 'SupplierAccounts',
meta: { meta: {
title: 'accounts', title: 'accounts',
icon: 'vn:account', icon: 'vn:credit',
}, },
component: () => component: () =>
import('src/pages/Supplier/Card/SupplierAccounts.vue'), import('src/pages/Supplier/Card/SupplierAccounts.vue'),

View File

@ -162,6 +162,15 @@ export const useInvoiceOutGlobalStore = defineStore({
); );
throw new Error('Invalid Serial Type'); throw new Error('Invalid Serial Type');
} }
if (clientsToInvoice === 'all' && params.serialType !== 'global') {
notify(
'invoiceOut.globalInvoices.errors.invalidSerialTypeForAll',
'negative'
);
throw new Error('For "all" clients, the serialType must be "global"');
}
if (!params.companyFk) { if (!params.companyFk) {
notify('invoiceOut.globalInvoices.errors.chooseValidCompany', 'negative'); notify('invoiceOut.globalInvoices.errors.chooseValidCompany', 'negative');
throw new Error('Invalid company'); throw new Error('Invalid company');

View File

@ -1,2 +1,2 @@
videos/* reports/*
screenshots/* screenshots/*

View File

@ -28,7 +28,7 @@ describe('Logout', () => {
}); });
it('when token not exists', () => { it('when token not exists', () => {
cy.get('.q-list > [href="#/item"]').click(); cy.get('.q-list').first().should('be.visible').click();
cy.checkNotification('Authorization Required'); cy.checkNotification('Authorization Required');
}); });
}); });

View File

@ -5,6 +5,7 @@ describe('VnSearchBar', () => {
const idGap = '.q-item > .q-item__label'; const idGap = '.q-item > .q-item__label';
const vnTableRow = '.q-virtual-scroll__content'; const vnTableRow = '.q-virtual-scroll__content';
beforeEach(() => { beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer'); cy.login('developer');
cy.visit('#/customer/list'); cy.visit('#/customer/list');
}); });

View File

@ -106,7 +106,6 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => {
case 'select': case 'select':
cy.wrap(el).type(val); cy.wrap(el).type(val);
cy.get('.q-menu .q-item').contains(val).click(); cy.get('.q-menu .q-item').contains(val).click();
cy.get('body').click();
break; break;
case 'date': case 'date':
cy.wrap(el).type(val.split('-').join('')); cy.wrap(el).type(val.split('-').join(''));