0
0
Fork 0

Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6942-improveInvoceIn

This commit is contained in:
Jorge Penadés 2024-03-11 08:23:56 +01:00
commit 058b21669f
10 changed files with 919 additions and 395 deletions

View File

@ -71,13 +71,9 @@ const closeForm = () => {
<span ref="closeButton" class="close-icon" v-close-popup> <span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" /> <QIcon name="close" size="sm" />
</span> </span>
<h1 class="title"> <span class="title">{{ t('Edit') }}</span>
{{ <span class="countLines">{{ ` ${rows.length} ` }}</span>
t('editBuyTitle', { <span class="title">{{ t('buy(s)') }}</span>
buysAmount: rows.length,
})
}}
</h1>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<VnSelectFilter <VnSelectFilter
@ -94,23 +90,23 @@ const closeForm = () => {
</div> </div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
<QBtn <QBtn
:label="t('globals.cancel')" :label="t('globals.cancel')"
type="reset" type="reset"
color="primary" color="primary"
flat flat
class="q-ml-sm"
:disabled="isLoading" :disabled="isLoading"
:loading="isLoading" :loading="isLoading"
v-close-popup v-close-popup
/> />
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
/>
</div> </div>
</QCard> </QCard>
</QForm> </QForm>
@ -129,13 +125,18 @@ const closeForm = () => {
right: 20px; right: 20px;
cursor: pointer; cursor: pointer;
} }
.countLines {
font-size: 24px;
color: $primary;
font-weight: bold;
}
</style> </style>
<i18n> <i18n>
en: es:
editBuyTitle: Edit {buysAmount} buy(s) Edit: Editar
es: buy(s): compra(s)
editBuyTitle: Editar {buysAmount} compra(s) Field to edit: Campo a editar
Field to edit: Campo a editar Value: Valor
Value: Valor </i18n>
</i18n>

View File

@ -1036,7 +1036,6 @@ en:
claimStateFk: Claim State claimStateFk: Claim State
workerFk: Worker workerFk: Worker
clientFk: Customer clientFk: Customer
rma: RMA
responsibility: Responsibility responsibility: Responsibility
packages: Packages packages: Packages
es: es:
@ -1076,7 +1075,6 @@ es:
claimStateFk: Estado de la reclamación claimStateFk: Estado de la reclamación
workerFk: Trabajador workerFk: Trabajador
clientFk: Cliente clientFk: Cliente
rma: RMA
responsibility: Responsabilidad responsibility: Responsabilidad
packages: Bultos packages: Bultos
</i18n> </i18n>

View File

@ -498,11 +498,9 @@ export default {
claims: 'Claims', claims: 'Claims',
list: 'List', list: 'List',
createClaim: 'Create claim', createClaim: 'Create claim',
rmaList: 'RMA',
summary: 'Summary', summary: 'Summary',
basicData: 'Basic Data', basicData: 'Basic Data',
lines: 'Lines', lines: 'Lines',
rma: 'RMA',
photos: 'Photos', photos: 'Photos',
development: 'Development', development: 'Development',
log: 'Audit logs', log: 'Audit logs',
@ -519,10 +517,6 @@ export default {
code: 'Code', code: 'Code',
records: 'records', records: 'records',
}, },
rma: {
user: 'User',
created: 'Created',
},
card: { card: {
claimId: 'Claim ID', claimId: 'Claim ID',
assignedTo: 'Assigned', assignedTo: 'Assigned',
@ -563,7 +557,6 @@ export default {
responsible: 'Responsible', responsible: 'Responsible',
worker: 'Worker', worker: 'Worker',
redelivery: 'Redelivery', redelivery: 'Redelivery',
returnOfMaterial: 'RMA',
}, },
basicData: { basicData: {
customer: 'Customer', customer: 'Customer',
@ -571,7 +564,6 @@ export default {
created: 'Created', created: 'Created',
state: 'State', state: 'State',
picked: 'Picked', picked: 'Picked',
returnOfMaterial: 'Return of material authorization (RMA)',
}, },
photo: { photo: {
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}', fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}',

View File

@ -497,11 +497,9 @@ export default {
claims: 'Reclamaciones', claims: 'Reclamaciones',
list: 'Listado', list: 'Listado',
createClaim: 'Crear reclamación', createClaim: 'Crear reclamación',
rmaList: 'RMA',
summary: 'Resumen', summary: 'Resumen',
basicData: 'Datos básicos', basicData: 'Datos básicos',
lines: 'Líneas', lines: 'Líneas',
rma: 'RMA',
development: 'Trazabilidad', development: 'Trazabilidad',
photos: 'Fotos', photos: 'Fotos',
log: 'Historial', log: 'Historial',
@ -518,10 +516,6 @@ export default {
code: 'Código', code: 'Código',
records: 'registros', records: 'registros',
}, },
rma: {
user: 'Usuario',
created: 'Creado',
},
card: { card: {
claimId: 'ID reclamación', claimId: 'ID reclamación',
assignedTo: 'Asignada a', assignedTo: 'Asignada a',
@ -562,7 +556,6 @@ export default {
responsible: 'Responsable', responsible: 'Responsable',
worker: 'Trabajador', worker: 'Trabajador',
redelivery: 'Devolución', redelivery: 'Devolución',
returnOfMaterial: 'RMA',
}, },
basicData: { basicData: {
customer: 'Cliente', customer: 'Cliente',
@ -570,7 +563,6 @@ export default {
created: 'Creada', created: 'Creada',
state: 'Estado', state: 'Estado',
picked: 'Recogida', picked: 'Recogida',
returnOfMaterial: 'Autorización de retorno de materiales (RMA)',
}, },
photo: { photo: {
fileDescription: fileDescription:

View File

@ -24,7 +24,6 @@ const claimFilter = {
'workerFk', 'workerFk',
'claimStateFk', 'claimStateFk',
'packages', 'packages',
'rma',
'hasToPickUp', 'hasToPickUp',
], ],
include: [ include: [
@ -169,13 +168,6 @@ const statesFilter = {
type="number" type="number"
/> />
</div> </div>
<div class="col">
<VnInput
v-model="data.rma"
:label="t('claim.basicData.returnOfMaterial')"
:rules="validate('claim.rma')"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">

View File

@ -1,145 +0,0 @@
<script setup>
import axios from 'axios';
import { watch, ref, computed, onUnmounted, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import CrudModel from 'components/CrudModel.vue';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
const quasar = useQuasar();
const state = useState();
const { t } = useI18n();
const selected = ref([]);
const claimRmaRef = ref();
const claim = computed(() => state.get('ClaimDescriptor'));
const claimRmaFilter = {
include: {
relation: 'worker',
scope: {
include: {
relation: 'user',
},
},
},
order: 'created DESC',
where: {
code: claim.value?.rma,
},
};
async function addRow() {
if (!claim.value.rma) {
return quasar.notify({
message: `This claim is not associated to any RMA`,
type: 'negative',
});
}
const formData = {
code: claim.value.rma,
};
await axios.post(`ClaimRmas`, formData);
await claimRmaRef.value.reload();
quasar.notify({
type: 'positive',
message: t('globals.rowAdded'),
icon: 'check',
});
}
onMounted(() => {
if (claim.value) claimRmaRef.value.reload();
});
watch(
claim,
() => {
claimRmaRef.value.reload();
},
{ deep: true }
);
</script>
<template>
<div class="column items-center">
<div class="list">
<CrudModel
data-key="ClaimRma"
url="ClaimRmas"
model="ClaimRma"
:filter="claimRmaFilter"
v-model:selected="selected"
ref="claimRmaRef"
:default-save="false"
:default-reset="false"
:default-remove="false"
>
<template #body="{ rows }">
<QCard>
<template v-for="(row, index) of rows" :key="row.id">
<QItem class="q-pa-none items-start">
<QItemSection class="q-pa-md">
<QList>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>
{{ t('claim.rma.user') }}
</QItemLabel>
<QItemLabel>
{{ row?.worker?.user?.name }}
</QItemLabel>
</QItemSection>
</QItem>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>
{{ t('claim.rma.created') }}
</QItemLabel>
<QItemLabel>
{{
toDate(row.created, {
timeStyle: 'medium',
})
}}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QItemSection>
<QCardActions vertical class="justify-between">
<QBtn
flat
round
color="orange"
icon="vn:bin"
@click="claimRmaRef.remove([row])"
>
<QTooltip>{{ t('globals.remove') }}</QTooltip>
</QBtn>
</QCardActions>
</QItem>
<QSeparator v-if="index !== rows.length - 1" />
</template>
</QCard>
</template>
</CrudModel>
</div>
</div>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="addRow()" />
</QPageSticky>
</template>
<style lang="scss" scoped>
.list {
width: 100%;
max-width: 60em;
}
</style>
<i18n>
es:
This claim is not associated to any RMA: Esta reclamación no está asociada a ninguna ARM
</i18n>

View File

@ -1,171 +0,0 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios';
const quasar = useQuasar();
const { t } = useI18n();
const arrayData = useArrayData('ClaimRmaList');
const isLoading = ref(false);
const input = ref();
const newRma = ref({
code: '',
crated: Date.vnNew(),
});
function onInputUpdate(value) {
newRma.value.code = value.toUpperCase();
}
async function submit() {
const formData = newRma.value;
if (formData.code === '') return;
isLoading.value = true;
await axios.post('ClaimRmas', formData);
await arrayData.refresh();
isLoading.value = false;
input.value.$el.focus();
newRma.value = {
code: '',
created: Date.vnNew(),
};
}
function confirm(id) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
data: { id },
promise: remove,
},
})
.onOk(async () => await arrayData.refresh());
}
async function remove({ id }) {
await axios.delete(`ClaimRmas/${id}`);
quasar.notify({
type: 'positive',
message: t('globals.rowRemoved'),
});
}
</script>
<template>
<QPage class="column items-center q-pa-md sticky">
<QPageSticky expand position="top" :offset="[16, 16]">
<QCard class="card q-pa-md">
<QForm @submit="submit">
<VnInput
ref="input"
v-model="newRma.code"
:label="t('claim.rmaList.code')"
@update:model-value="onInputUpdate"
class="q-mb-md"
:readonly="isLoading"
:loading="isLoading"
autofocus
/>
<div class="text-caption">
{{ arrayData.totalRows }} {{ t('claim.rmaList.records') }}
</div>
</QForm>
</QCard>
</QPageSticky>
<div class="vn-card-list">
<VnPaginate
data-key="ClaimRmaList"
url="ClaimRmas"
order="id DESC"
:offset="50"
auto-load
>
<template #body="{ rows }">
<QCard class="card">
<template v-if="isLoading">
<QItem class="q-pa-none items-start">
<QItemSection class="q-pa-md">
<QList>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>
<QSkeleton />
</QItemLabel>
<QItemLabel>
<QSkeleton type="text" />
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QItemSection>
<QCardActions vertical class="justify-between">
<QSkeleton
type="circle"
class="q-mb-md"
size="40px"
/>
</QCardActions>
</QItem>
<QSeparator />
</template>
<template v-for="row of rows" :key="row.id">
<QItem class="q-pa-none items-start">
<QItemSection class="q-pa-md">
<QList>
<QItem class="q-pa-none">
<QItemSection>
<QItemLabel caption>{{
t('claim.rmaList.code')
}}</QItemLabel>
<QItemLabel>{{ row.code }}</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QItemSection>
<QCardActions vertical class="justify-between">
<QBtn
flat
round
color="primary"
icon="vn:bin"
@click="confirm(row.id)"
>
<QTooltip>{{ t('globals.remove') }}</QTooltip>
</QBtn>
</QCardActions>
</QItem>
<QSeparator />
</template>
</QCard>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<style lang="scss" scoped>
.sticky {
padding-top: 156px;
}
.card {
width: 100%;
max-width: 60em;
}
.q-page-sticky {
z-index: 2998;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed, reactive, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -8,11 +8,16 @@ import FetchedTags from 'components/ui/FetchedTags.vue';
import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue'; import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue'; import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue'; import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import { useSession } from 'composables/useSession'; import { useSession } from 'composables/useSession';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
const router = useRouter(); const router = useRouter();
const session = useSession(); const session = useSession();
@ -21,11 +26,72 @@ const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const rowsFetchDataRef = ref(null); const rowsFetchDataRef = ref(null);
const itemTypesOptions = ref([]);
const originsOptions = ref([]);
const itemFamiliesOptions = ref([]);
const intrastatOptions = ref([]);
const packagingsOptions = ref([]);
const editTableCellDialogRef = ref(null); const editTableCellDialogRef = ref(null);
const visibleColumns = ref([]); const visibleColumns = ref([]);
const allColumnNames = ref([]); const allColumnNames = ref([]);
const rows = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'id':
case 'size':
case 'weightByPiece':
case 'isActive':
case 'family':
case 'minPrice':
case 'packingOut':
return { [`i.${param}`]: value };
case 'name':
case 'description':
return { [`i.${param}`]: { like: `%${value}%` } };
case 'code':
return { 'it.code': value };
case 'intrastat':
return { 'intr.description': value };
case 'origin':
return { 'ori.code': value };
case 'landing':
return { [`lb.${param}`]: value };
case 'packing':
case 'grouping':
case 'quantity':
case 'entryFk':
case 'buyingValue':
case 'freightValue':
case 'comissionValue':
case 'packageValue':
case 'isIgnored':
case 'price2':
case 'price3':
case 'ektFk':
case 'weight':
case 'packagingFk':
return { [`b.${param}`]: value };
}
};
const params = reactive({});
const arrayData = useArrayData('EntryLatestBuys', {
url: 'Buys/latestBuysFilter',
order: ['itemFk DESC'],
exprBuilder: exprBuilder,
});
const store = arrayData.store;
const rows = computed(() => store.data);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [ const columns = computed(() => [
{ {
label: t('entry.latestBuys.picture'), label: t('entry.latestBuys.picture'),
@ -37,12 +103,32 @@ const columns = computed(() => [
name: 'itemFk', name: 'itemFk',
field: 'itemFk', field: 'itemFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packing'), label: t('entry.latestBuys.packing'),
field: 'packing', field: 'packing',
name: 'packing', name: 'packing',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -50,6 +136,16 @@ const columns = computed(() => [
field: 'grouping', field: 'grouping',
name: 'grouping', name: 'grouping',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -57,12 +153,32 @@ const columns = computed(() => [
field: 'quantity', field: 'quantity',
name: 'quantity', name: 'quantity',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('globals.description'), label: t('globals.description'),
field: 'description', field: 'description',
name: 'description', name: 'description',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -70,35 +186,104 @@ const columns = computed(() => [
field: 'size', field: 'size',
name: 'size', name: 'size',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.tags'), label: t('entry.latestBuys.tags'),
name: 'tags', name: 'tags',
align: 'left', align: 'left',
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.type'), label: t('entry.latestBuys.type'),
field: 'code', field: 'code',
name: 'type', name: 'type',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: itemTypesOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.intrastat'), label: t('entry.latestBuys.intrastat'),
field: 'intrastat', field: 'intrastat',
name: 'intrastat', name: 'intrastat',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: intrastatOptions.value,
'option-value': 'description',
'option-label': 'description',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.origin'), label: t('entry.latestBuys.origin'),
field: 'origin', field: 'origin',
name: 'origin', name: 'origin',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: originsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.weightByPiece'), label: t('entry.latestBuys.weightByPiece'),
field: 'weightByPiece', field: 'weightByPiece',
name: 'weightByPiece', name: 'weightByPiece',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -106,24 +291,67 @@ const columns = computed(() => [
field: 'isActive', field: 'isActive',
name: 'isActive', name: 'isActive',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.family'), label: t('entry.latestBuys.family'),
field: 'family', field: 'family',
name: 'family', name: 'family',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: itemFamiliesOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.entryFk'), label: t('entry.latestBuys.entryFk'),
field: 'entryFk', field: 'entryFk',
name: 'entryFk', name: 'entryFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.buyingValue'), label: t('entry.latestBuys.buyingValue'),
field: 'buyingValue', field: 'buyingValue',
name: 'buyingValue', name: 'buyingValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -131,6 +359,16 @@ const columns = computed(() => [
field: 'freightValue', field: 'freightValue',
name: 'freightValue', name: 'freightValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -138,6 +376,16 @@ const columns = computed(() => [
field: 'comissionValue', field: 'comissionValue',
name: 'comissionValue', name: 'comissionValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -145,6 +393,16 @@ const columns = computed(() => [
field: 'packageValue', field: 'packageValue',
name: 'packageValue', name: 'packageValue',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -152,12 +410,33 @@ const columns = computed(() => [
field: 'isIgnored', field: 'isIgnored',
name: 'isIgnored', name: 'isIgnored',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.price2'), label: t('entry.latestBuys.price2'),
field: 'price2', field: 'price2',
name: 'price2', name: 'price2',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -165,6 +444,16 @@ const columns = computed(() => [
field: 'price3', field: 'price3',
name: 'price3', name: 'price3',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -172,6 +461,16 @@ const columns = computed(() => [
field: 'minPrice', field: 'minPrice',
name: 'minPrice', name: 'minPrice',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val), format: (val) => toCurrency(val),
}, },
{ {
@ -179,6 +478,16 @@ const columns = computed(() => [
field: 'ektFk', field: 'ektFk',
name: 'ektFk', name: 'ektFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -186,18 +495,51 @@ const columns = computed(() => [
field: 'weight', field: 'weight',
name: 'weight', name: 'weight',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packagingFk'), label: t('entry.latestBuys.packagingFk'),
field: 'packagingFk', field: 'packagingFk',
name: 'packagingFk', name: 'packagingFk',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnSelectFilter,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: packagingsOptions.value,
'option-value': 'id',
'option-label': 'id',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packingOut'), label: t('entry.latestBuys.packingOut'),
field: 'packingOut', field: 'packingOut',
name: 'packingOut', name: 'packingOut',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val), format: (val) => dashIfEmpty(val),
}, },
{ {
@ -205,6 +547,16 @@ const columns = computed(() => [
field: 'landing', field: 'landing',
name: 'landing', name: 'landing',
align: 'left', align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toDate(val), format: (val) => toDate(val),
}, },
]); ]);
@ -234,20 +586,55 @@ const redirectToEntryBuys = (entryFk) => {
router.push({ name: 'EntryBuys', params: { id: entryFk } }); router.push({ name: 'EntryBuys', params: { id: entryFk } });
}; };
const applyColumnFilter = async (col) => {
try {
params[col.field] = col.columnFilter.filterValue;
await arrayData.addFilter({ params });
} catch (err) {
console.error('Error applying column filter', err);
}
};
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
const filteredColumns = columns.value.filter((col) => col.name !== 'picture'); const filteredColumns = columns.value.filter((col) => col.name !== 'picture');
allColumnNames.value = filteredColumns.map((col) => col.name); allColumnNames.value = filteredColumns.map((col) => col.name);
await arrayData.fetch({ append: false });
}); });
onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
<FetchData <FetchData
ref="rowsFetchDataRef" url="ItemTypes"
url="Buys/latestBuysFilter" :filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
:filter="{ order: 'itemFk DESC', limit: 20 }"
@on-fetch="(data) => (rows = data)"
auto-load auto-load
@on-fetch="(data) => (itemTypesOptions = data)"
/>
<FetchData
url="Origins"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (originsOptions = data)"
/>
<FetchData
url="ItemFamilies"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (itemFamiliesOptions = data)"
/>
<FetchData
url="Packagings"
:filter="{ fields: ['id'], order: 'id ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (packagingsOptions = data)"
/>
<FetchData
url="Intrastats"
:filter="{ fields: ['description'], order: 'description ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (intrastatOptions = data)"
/> />
<QToolbar class="bg-vn-dark justify-end"> <QToolbar class="bg-vn-dark justify-end">
<div id="st-data"> <div id="st-data">
@ -261,19 +648,43 @@ onMounted(async () => {
<QSpace /> <QSpace />
<div id="st-actions"></div> <div id="st-actions"></div>
</QToolbar> </QToolbar>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<EntryLatestBuysFilter data-key="EntryLatestBuys" :tags="tags" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <QTable
:rows="rows" :rows="rows"
:columns="columns" :columns="columns"
hide-bottom
selection="multiple" selection="multiple"
row-key="id" row-key="id"
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
class="full-width q-mt-md" class="full-width q-mt-md"
:visible-columns="visibleColumns" :visible-columns="visibleColumns"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
:no-data-label="t('globals.noResults')"
@row-click="(_, row) => redirectToEntryBuys(row.entryFk)" @row-click="(_, row) => redirectToEntryBuys(row.entryFk)"
> >
<template #top-row="{ cols }">
<QTr>
<QTd />
<QTd
v-for="(col, index) in cols"
:key="index"
style="max-width: 100px"
>
<component
:is="col.columnFilter.component"
v-if="col.name !== 'picture'"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-picture="{ row }"> <template #body-cell-picture="{ row }">
<QTd> <QTd>
<QImg <QImg
@ -288,9 +699,10 @@ onMounted(async () => {
</template> </template>
<template #body-cell-itemFk="{ row }"> <template #body-cell-itemFk="{ row }">
<QTd @click.stop> <QTd @click.stop>
<QBtn flat color="blue"> <QBtn flat color="primary">
{{ row.itemFk }} {{ row.itemFk }}
</QBtn> </QBtn>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd> </QTd>
</template> </template>
<template #body-cell-tags="{ row }"> <template #body-cell-tags="{ row }">
@ -300,7 +712,7 @@ onMounted(async () => {
</template> </template>
<template #body-cell-entryFk="{ row }"> <template #body-cell-entryFk="{ row }">
<QTd @click.stop> <QTd @click.stop>
<QBtn flat color="blue"> <QBtn flat color="primary">
<EntryDescriptorProxy :id="row.entryFk" /> <EntryDescriptorProxy :id="row.entryFk" />
{{ row.entryFk }} {{ row.entryFk }}
</QBtn> </QBtn>

View File

@ -0,0 +1,474 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import axios from 'axios';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const itemCategories = ref([]);
const selectedCategoryFk = ref(null);
const selectedTypeFk = ref(null);
const itemTypesOptions = ref([]);
const itemTypeWorkersOptions = ref([]);
const suppliersOptions = ref([]);
const tagOptions = ref([]);
const tagValues = ref([]);
const categoryList = computed(() => {
return (itemCategories.value || [])
.filter((category) => category.display)
.map((category) => ({
...category,
icon: `vn:${(category.icon || '').split('-')[1]}`,
}));
});
const selectedCategory = computed(() =>
(itemCategories.value || []).find(
(category) => category?.id === selectedCategoryFk.value
)
);
const selectedType = computed(() => {
return (itemTypesOptions.value || []).find(
(type) => type?.id === selectedTypeFk.value
);
});
const selectCategory = async (params, categoryId, search) => {
if (params.categoryFk === categoryId) {
resetCategory(params);
search();
return;
}
selectedCategoryFk.value = categoryId;
params.categoryFk = categoryId;
await fetchItemTypes(categoryId);
search();
};
const resetCategory = (params) => {
selectedCategoryFk.value = null;
itemTypesOptions.value = null;
if (params) {
params.categoryFk = null;
params.typeFk = null;
}
};
const applyTags = (params, search) => {
params.tags = tagValues.value
.filter((tag) => tag.selectedTag && tag.value)
.map((tag) => ({
tagFk: tag.selectedTag.id,
tagName: tag.selectedTag.name,
value: tag.value,
}));
search();
};
const fetchItemTypes = async (id) => {
try {
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
} catch (err) {
console.error('Error fetching item types', err);
}
};
const getCategoryClass = (category, params) => {
if (category.id === params?.categoryFk) {
return 'active';
}
};
const getSelectedTagValues = async (tag) => {
try {
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
} catch (err) {
console.error('Error getting selected tag values');
}
};
const removeTag = (index, params, search) => {
(tagValues.value || []).splice(index, 1);
applyTags(params, search);
};
</script>
<template>
<FetchData
url="ItemCategories"
limit="30"
auto-load
@on-fetch="(data) => (itemCategories = data)"
/>
<FetchData
url="TicketRequests/getItemTypeWorker"
limit="30"
auto-load
:filter="{ fields: ['id', 'nickname'], order: 'nickname ASC', limit: 30 }"
@on-fetch="(data) => (itemTypeWorkersOptions = data)"
/>
<FetchData
url="Suppliers"
limit="30"
auto-load
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
@on-fetch="(data) => (suppliersOptions = data)"
/>
<FetchData
url="Tags"
:filter="{ fields: ['id', 'name', 'isFree'] }"
auto-load
limit="30"
@on-fetch="(data) => (tagOptions = data)"
/>
<VnFilterPanel
:data-key="props.dataKey"
:expr-builder="exprBuilder"
:custom-tags="['tags']"
@init="onFilterInit"
@remove="clearFilter"
>
<template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'">
{{ t(selectedCategory?.name || '') }}
</strong>
<strong v-else-if="tag.label === 'typeFk'">
{{ t(selectedType?.name || '') }}
</strong>
<div v-else class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #customTags="{ tags: customTags, params }">
<template v-for="tag in customTags" :key="tag.label">
<VnFilterPanelChip
v-for="chip in tag.value"
:key="chip"
removable
@remove="removeTagChip(chip, params, searchFn)"
>
<div class="q-gutter-x-xs">
<strong>{{ chip.tagName }}: </strong>
<span>"{{ chip.value }}"</span>
</div>
</VnFilterPanelChip>
</template>
</template>
<template #body="{ params, searchFn }">
<QItem class="category-filter q-mt-md">
<QBtn
dense
flat
round
v-for="category in categoryList"
:key="category.name"
:class="['category', getCategoryClass(category, params)]"
:icon="category.icon"
@click="selectCategory(params, category.id, searchFn)"
>
<QTooltip>
{{ t(category.name) }}
</QTooltip>
</QBtn>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.typeFk')"
v-model="params.typeFk"
:options="itemTypesOptions"
option-value="id"
option-label="name"
dense
outlined
rounded
use-input
:disable="!selectedCategoryFk"
@update:model-value="
(value) => {
selectedTypeFk = value;
searchFn();
}
"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.categoryName }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
<QSeparator />
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.salesPersonFk')"
v-model="params.salesPersonFk"
:options="itemTypeWorkersOptions"
option-value="id"
option-label="nickname"
dense
outlined
rounded
use-input
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.supplier')"
v-model="params.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="name"
dense
outlined
rounded
use-input
@update:model-value="searchFn()"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('params.from')"
v-model="params.from"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('params.to')"
v-model="params.to"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.active')"
v-model="params.active"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('params.visible')"
v-model="params.visible"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('params.floramondo')"
v-model="params.floramondo"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem
v-for="(value, index) in tagValues"
:key="value"
class="q-mt-md filter-value"
>
<QItemSection class="col">
<VnSelectFilter
:label="t('params.tag')"
v-model="value.selectedTag"
:options="tagOptions"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="getSelectedTagValues(value)"
/>
</QItemSection>
<QItemSection class="col">
<VnSelectFilter
v-if="!value?.selectedTag?.isFree && value.valueOptions"
:label="t('params.value')"
v-model="value.value"
:options="value.valueOptions || []"
option-value="value"
option-label="value"
dense
outlined
rounded
emit-value
use-input
:disable="!value"
:is-clearable="false"
class="filter-input"
@update:model-value="applyTags(params, searchFn)"
/>
<VnInput
v-else
v-model="value.value"
:label="t('params.value')"
:disable="!value"
is-outlined
class="filter-input"
:is-clearable="false"
@keyup.enter="applyTags(params, searchFn)"
/>
</QItemSection>
<QIcon
name="delete"
class="filter-icon"
@click="removeTag(index, params, searchFn)"
/>
</QItem>
<QItem class="q-mt-lg">
<QIcon
name="add_circle"
class="filter-icon"
@click="tagValues.push({})"
/>
</QItem>
</template>
</VnFilterPanel>
</template>
<style lang="scss" scoped>
.category-filter {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
.category {
padding: 8px;
width: 60px;
height: 60px;
font-size: 1.4rem;
background-color: var(--vn-light-gray);
&.active {
background-color: $primary;
}
}
}
.filter-icon {
font-size: 24px;
color: $primary;
padding: 0 4px;
cursor: pointer;
}
.filter-input {
flex-shrink: 1;
min-width: 0;
}
.filter-value {
display: flex;
align-items: center;
}
</style>
<i18n>
en:
params:
supplier: Supplier
from: From
to: To
active: Is active
visible: Is visible
floramondo: Is floramondo
salesPersonFk: Buyer
categoryFk: Category
typeFk: Type
tag: Tag
value: Value
es:
params:
supplier: Proveedor
from: Desde
to: Hasta
active: Activo
visible: Visible
floramondo: Floramondo
salesPersonFk: Comprador
categoryFk: Categoría
typeFk: Tipo
tag: Etiqueta
value: Valor
Plant: Planta
Flower: Flor
Handmade: Confección
Green: Verde
Accessories: Complemento
Fruit: Fruta
</i18n>

View File

@ -10,11 +10,10 @@ export default {
component: RouterView, component: RouterView,
redirect: { name: 'ClaimMain' }, redirect: { name: 'ClaimMain' },
menus: { menus: {
main: ['ClaimList', 'ClaimRmaList'], main: ['ClaimList'],
card: [ card: [
'ClaimBasicData', 'ClaimBasicData',
'ClaimLines', 'ClaimLines',
'ClaimRma',
'ClaimPhotos', 'ClaimPhotos',
'ClaimNotes', 'ClaimNotes',
'ClaimDevelopment', 'ClaimDevelopment',
@ -38,16 +37,6 @@ export default {
}, },
component: () => import('src/pages/Claim/ClaimList.vue'), component: () => import('src/pages/Claim/ClaimList.vue'),
}, },
{
name: 'ClaimRmaList',
path: 'rma',
meta: {
title: 'rmaList',
icon: 'vn:barcode',
roles: ['claimManager'],
},
component: () => import('src/pages/Claim/ClaimRmaList.vue'),
},
], ],
}, },
{ {
@ -84,16 +73,6 @@ export default {
}, },
component: () => import('src/pages/Claim/Card/ClaimLines.vue'), component: () => import('src/pages/Claim/Card/ClaimLines.vue'),
}, },
{
name: 'ClaimRma',
path: 'rma',
meta: {
title: 'rma',
icon: 'vn:barcode',
roles: ['claimManager'],
},
component: () => import('src/pages/Claim/Card/ClaimRma.vue'),
},
{ {
name: 'ClaimPhotos', name: 'ClaimPhotos',
path: 'photos', path: 'photos',