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

This commit is contained in:
Alex Moreno 2025-03-24 14:27:54 +01:00
commit 6a97c368e4
15 changed files with 297 additions and 40 deletions

View File

@ -897,7 +897,7 @@ const rowCtrlClickFunction = computed(() => {
{{ row[splittedColumns.title.name] }} {{ row[splittedColumns.title.name] }}
</span> </span>
</QCardSection> </QCardSection>
<!-- Fields --> <!-- Fields -->
<QCardSection <QCardSection
class="q-pl-sm q-py-xs" class="q-pl-sm q-py-xs"
:class="$props.cardClass" :class="$props.cardClass"
@ -1156,7 +1156,7 @@ es:
.grid-create { .grid-create {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
max-width: 100%; max-width: 100%;
grid-gap: 20px; grid-gap: 20px;
margin: 0 auto; margin: 0 auto;

View File

@ -0,0 +1,166 @@
<script setup>
import VnConfirm from '../ui/VnConfirm.vue';
import VnInput from './VnInput.vue';
import VnDms from './VnDms.vue';
import axios from 'axios';
import { useQuasar } from 'quasar';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { downloadFile } from 'src/composables/downloadFile';
const { t } = useI18n();
const quasar = useQuasar();
const documentDialogRef = ref({});
const editDownloadDisabled = ref(false);
const $props = defineProps({
defaultDmsCode: {
type: String,
default: 'InvoiceIn',
},
disable: {
type: Boolean,
default: true,
},
data: {
type: Object,
default: null,
},
formRef: {
type: Object,
default: null,
},
});
function deleteFile(dmsFk) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'),
},
})
.onOk(async () => {
await axios.post(`dms/${dmsFk}/removeFile`);
$props.formRef.formData.dmsFk = null;
$props.formRef.formData.dms = undefined;
$props.formRef.hasChanges = true;
$props.formRef.save();
});
}
</script>
<template>
<div class="row no-wrap">
<VnInput
:label="t('Document')"
v-model="data.dmsFk"
clearable
clear-icon="close"
class="full-width"
:disable="disable"
/>
<div
v-if="data.dmsFk"
class="row no-wrap q-pa-xs q-gutter-x-xs"
data-cy="dms-buttons"
>
<QBtn
:disable="editDownloadDisabled"
@click="downloadFile(data.dmsFk)"
icon="cloud_download"
color="primary"
flat
:class="{
'no-pointer-events': editDownloadDisabled,
}"
padding="xs"
round
>
<QTooltip>{{ t('Download file') }}</QTooltip>
</QBtn>
<QBtn
:disable="editDownloadDisabled"
@click="
() => {
documentDialogRef.show = true;
documentDialogRef.dms = data.dms;
}
"
icon="edit"
color="primary"
flat
:class="{
'no-pointer-events': editDownloadDisabled,
}"
padding="xs"
round
>
<QTooltip>{{ t('Edit document') }}</QTooltip>
</QBtn>
<QBtn
:disable="editDownloadDisabled"
@click="deleteFile(data.dmsFk)"
icon="delete"
color="primary"
flat
round
:class="{
'no-pointer-events': editDownloadDisabled,
}"
padding="xs"
>
<QTooltip>{{ t('Delete file') }}</QTooltip>
</QBtn>
</div>
<QBtn
v-else
icon="add_circle"
color="primary"
flat
round
v-shortcut="'+'"
padding="xs"
@click="
() => {
documentDialogRef.show = true;
delete documentDialogRef.dms;
}
"
data-cy="dms-create"
>
<QTooltip>{{ t('Create document') }}</QTooltip>
</QBtn>
</div>
<QDialog v-model="documentDialogRef.show">
<VnDms
model="dms"
:default-dms-code="defaultDmsCode"
:form-initial-data="documentDialogRef.dms"
:url="
documentDialogRef.dms
? `Dms/${documentDialogRef.dms.id}/updateFile`
: 'Dms/uploadFile'
"
:description="documentDialogRef.supplierName"
@on-data-saved="
(_, { data }) => {
let dmsData = data;
if (Array.isArray(data)) dmsData = data[0];
formRef.formData.dmsFk = dmsData.id;
formRef.formData.dms = dmsData;
formRef.hasChanges = true;
formRef.save();
}
"
/>
</QDialog>
</template>
<i18n>
es:
Document: Documento
Download file: Descargar archivo
Edit document: Editar documento
Delete file: Eliminar archivo
Create document: Crear documento
</i18n>

View File

@ -84,7 +84,7 @@ const mixinRules = [
...($attrs.rules ?? []), ...($attrs.rules ?? []),
(val) => { (val) => {
const maxlength = $props.maxlength; const maxlength = $props.maxlength;
if (maxlength && +val.length > maxlength) if (maxlength && +val?.length > maxlength)
return t(`maxLength`, { value: maxlength }); return t(`maxLength`, { value: maxlength });
const { min, max } = vnInputRef.value.$attrs; const { min, max } = vnInputRef.value.$attrs;
if (!min) return null; if (!min) return null;

View File

@ -325,7 +325,6 @@ input::-webkit-inner-spin-button {
min-height: auto !important; min-height: auto !important;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
padding-bottom: 2px;
.q-field__native.row { .q-field__native.row {
min-height: auto !important; min-height: auto !important;
} }

View File

@ -817,6 +817,7 @@ travel:
search: Search travel search: Search travel
searchInfo: You can search by travel id or name searchInfo: You can search by travel id or name
id: Id id: Id
awbFk: AWB
travelList: travelList:
tableVisibleColumns: tableVisibleColumns:
ref: Reference ref: Reference

View File

@ -900,6 +900,7 @@ travel:
search: Buscar envío search: Buscar envío
searchInfo: Buscar envío por id o nombre searchInfo: Buscar envío por id o nombre
id: Id id: Id
awbFk: Guía aérea
travelList: travelList:
tableVisibleColumns: tableVisibleColumns:
ref: Referencia ref: Referencia

View File

@ -14,6 +14,8 @@ import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue'; import VnCheckbox from 'src/components/common/VnCheckbox.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import VnDmsInput from 'src/components/common/VnDmsInput.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -24,6 +26,7 @@ const user = state.getUser().fn();
const companiesOptions = ref([]); const companiesOptions = ref([]);
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
const entryRef = ref({});
onMounted(() => { onMounted(() => {
checkEntryLock(route.params.id, user.id); checkEntryLock(route.params.id, user.id);
@ -48,10 +51,11 @@ onMounted(() => {
auto-load auto-load
/> />
<FormModel <FormModel
:url-update="`Entries/${route.params.id}`" ref="entryRef"
model="Entry" model="Entry"
auto-load :url-update="`Entries/${route.params.id}`"
:clear-store-on-unmount="false" :clear-store-on-unmount="false"
auto-load
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow class="q-py-sm"> <VnRow class="q-py-sm">
@ -67,11 +71,18 @@ onMounted(() => {
/> />
</VnRow> </VnRow>
<VnRow class="q-py-sm"> <VnRow class="q-py-sm">
<VnInput v-model="data.reference" :label="t('globals.reference')" /> <VnInput
<VnInputNumber v-model="data.reference"
v-model="data.invoiceAmount" :label="t('entry.list.tableVisibleColumns.reference')"
:label="t('entry.summary.invoiceAmount')" />
:positive="false" <VnSelect
v-model="data.typeFk"
url="entryTypes"
:fields="['code', 'description']"
option-value="code"
optionLabel="description"
sortBy="description"
:label="t('entry.list.tableVisibleColumns.entryTypeDescription')"
/> />
</VnRow> </VnRow>
<VnRow class="q-py-sm"> <VnRow class="q-py-sm">
@ -113,7 +124,6 @@ onMounted(() => {
name="initialTemperature" name="initialTemperature"
:label="t('entry.basicData.initialTemperature')" :label="t('entry.basicData.initialTemperature')"
:step="0.5" :step="0.5"
:decimal-places="2"
:positive="false" :positive="false"
/> />
<VnInputNumber <VnInputNumber
@ -121,20 +131,21 @@ onMounted(() => {
name="finalTemperature" name="finalTemperature"
:label="t('entry.basicData.finalTemperature')" :label="t('entry.basicData.finalTemperature')"
:step="0.5" :step="0.5"
:decimal-places="2"
:positive="false" :positive="false"
/> />
<VnSelect
v-model="data.typeFk"
url="entryTypes"
:fields="['code', 'description']"
option-value="code"
optionLabel="description"
sortBy="description"
/>
</VnRow> </VnRow>
<VnRow class="q-py-sm"> <VnRow class="q-py-sm">
<QInput <VnInputNumber
v-model="data.invoiceAmount"
:label="t('entry.list.tableVisibleColumns.invoiceAmount')"
:positive="false"
@update:model-value="data.buyerFk = user.id"
/>
<VnSelectWorker v-model="data.buyerFk" hide-selected />
<VnDmsInput :data="data" :formRef="entryRef" :disable="false" />
</VnRow>
<VnRow class="q-py-sm">
<VnInputNumber
:label="t('entry.basicData.observation')" :label="t('entry.basicData.observation')"
type="textarea" type="textarea"
v-model="data.observation" v-model="data.observation"

View File

@ -18,6 +18,7 @@ import VnSelectEnum from 'src/components/common/VnSelectEnum.vue';
import { checkEntryLock } from 'src/composables/checkEntryLock'; import { checkEntryLock } from 'src/composables/checkEntryLock';
import VnRow from 'src/components/ui/VnRow.vue'; import VnRow from 'src/components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -44,6 +45,8 @@ const entityId = ref($props.id ?? route.params.id);
const entryBuysRef = ref(); const entryBuysRef = ref();
const footerFetchDataRef = ref(); const footerFetchDataRef = ref();
const footer = ref({}); const footer = ref({});
const dialogRef = ref(false);
const newEntryRef = ref(null);
const columns = [ const columns = [
{ {
align: 'center', align: 'center',
@ -250,6 +253,7 @@ const columns = [
component: 'number', component: 'number',
attrs: { attrs: {
positive: false, positive: false,
decimalPlaces: 3,
}, },
cellEvent: { cellEvent: {
'update:modelValue': async (value, oldValue, row) => { 'update:modelValue': async (value, oldValue, row) => {
@ -497,6 +501,23 @@ async function setBuyUltimate(itemFk, data) {
}); });
} }
async function transferBuys(rows, newEntry) {
if (!newEntry) return;
const promises = rows.map((row) => {
return axios.patch('Buys', { id: row.id, entryFk: newEntry });
});
await Promise.all(promises);
await axios.post(`Entries/${newEntry}/recalcEntryPrices`);
await axios.post(`Entries/${entityId.value}/recalcEntryPrices`);
entryBuysRef.value.reload();
newEntryRef.value = null;
dialogRef.value = false;
}
onMounted(() => { onMounted(() => {
stateStore.rightDrawer = false; stateStore.rightDrawer = false;
if ($props.editableMode) checkEntryLock(entityId.value, user.id); if ($props.editableMode) checkEntryLock(entityId.value, user.id);
@ -571,6 +592,47 @@ onMounted(() => {
</QItem> </QItem>
</QList> </QList>
</QBtnDropdown> </QBtnDropdown>
<QBtn
icon="move_group"
color="primary"
:title="t('Transfer buys')"
data-cy="transferBuys"
flat
@click="dialogRef = true"
:disable="!selectedRows.length"
/>
<QDialog v-model="dialogRef">
<QCard>
<QCardSection>
<span>{{ t('Transfer buys') }}</span>
</QCardSection>
<QCardSection>
<VnInputNumber
v-model="newEntryRef"
:label="t('Entry')"
type="number"
data-cy="entryDestinyInput"
/>
</QCardSection>
<QCardSection>
<QCardActions>
<QBtn
label="Cancel"
flat
color="primary"
@click="dialogRef = false"
/>
<QBtn
label="Transfer"
data-cy="transferBuysBtn"
flat
color="primary"
@click="transferBuys(selectedRows, newEntryRef)"
/>
</QCardActions>
</QCardSection>
</QCard>
</QDialog>
</QBtnGroup> </QBtnGroup>
</Teleport> </Teleport>
<FetchData <FetchData
@ -620,7 +682,7 @@ onMounted(() => {
}, },
columnGridStyle: { columnGridStyle: {
'max-width': '50%', 'max-width': '50%',
'margin-right': '30px', 'margin-right': '5%',
flex: 1, flex: 1,
}, },
previousStyle: { previousStyle: {
@ -816,6 +878,8 @@ es:
Create buy: Crear compra Create buy: Crear compra
Invert quantity value: Invertir valor de cantidad Invert quantity value: Invertir valor de cantidad
Check buy amount: Marcar como correcta la cantidad de compra Check buy amount: Marcar como correcta la cantidad de compra
Transfer buys: Transferir compras
Entry: Entrada
</i18n> </i18n>
<style lang="scss" scoped> <style lang="scss" scoped>
.centered-container { .centered-container {

View File

@ -92,7 +92,7 @@ const getEntryRedirectionFilter = (entry) => {
}; };
function showEntryReport() { function showEntryReport() {
openReport(`Entries/${entityId.value}/entry-order-pdf`); openReport(`Entries/${entityId.value}/entry-order-pdf`, {}, true);
} }
function showNotification(type, message) { function showNotification(type, message) {
@ -147,7 +147,7 @@ async function deleteEntry() {
<template> <template>
<EntityDescriptor <EntityDescriptor
:url="`Entries/${entityId}`" :url="`Entries/${entityId}`"
:filter="entryFilter" :user-filter="entryFilter"
title="supplier.nickname" title="supplier.nickname"
data-key="Entry" data-key="Entry"
width="lg-width" width="lg-width"

View File

@ -84,7 +84,10 @@ onMounted(async () => {
:label="t('globals.company')" :label="t('globals.company')"
:value="entry?.company?.code" :value="entry?.company?.code"
/> />
<VnLv :label="t('globals.reference')" :value="entry?.reference" /> <VnLv
:label="t('entry.list.tableVisibleColumns.reference')"
:value="entry?.reference"
/>
<VnLv <VnLv
:label="t('entry.summary.invoiceNumber')" :label="t('entry.summary.invoiceNumber')"
:value="entry?.invoiceNumber" :value="entry?.invoiceNumber"
@ -159,6 +162,7 @@ onMounted(async () => {
/> />
</div> </div>
<div class="card-content"> <div class="card-content">
<VnLv :label="t('travel.awbFk')" :value="entry.travel.awbFk" />
<VnCheckbox <VnCheckbox
:label="t('entry.summary.travelDelivered')" :label="t('entry.summary.travelDelivered')"
v-model="entry.travel.isDelivered" v-model="entry.travel.isDelivered"

View File

@ -162,8 +162,8 @@ async function beforeSave(data, getChanges) {
} }
await Promise.all(patchPromises); await Promise.all(patchPromises);
const filteredChanges = changes.filter((change) => change?.isReal !== false); data.creates = [];
data.creates = filteredChanges; return data;
} }
</script> </script>
<template> <template>
@ -203,7 +203,7 @@ async function beforeSave(data, getChanges) {
</VnRow> </VnRow>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QDialog v-model="travelDialogRef" :maximized="true" :class="['vn-row', 'wrap']"> <QDialog v-model="travelDialogRef" :class="['vn-row', 'wrap']">
<FormModelPopup <FormModelPopup
:url-update="`Travels/${travel?.id}`" :url-update="`Travels/${travel?.id}`"
model="travel" model="travel"
@ -252,12 +252,15 @@ async function beforeSave(data, getChanges) {
</span> </span>
</template> </template>
<template #column-footer-reserve> <template #column-footer-reserve>
<span> <span class="q-pr-xs">
{{ round(footer.reserve) }} {{ round(footer.reserve) }}
</span> </span>
</template> </template>
<template #column-footer-bought> <template #column-footer-bought>
<span :style="boughtStyle(footer?.bought, footer?.reserve)"> <span
:style="boughtStyle(footer?.bought, footer?.reserve)"
class="q-pr-xs"
>
{{ round(footer.bought) }} {{ round(footer.bought) }}
</span> </span>
</template> </template>
@ -275,7 +278,7 @@ async function beforeSave(data, getChanges) {
} }
.column { .column {
min-width: 35%; min-width: 35%;
margin-top: 5%; margin-top: 1%;
} }
.text-negative { .text-negative {
color: $negative !important; color: $negative !important;

View File

@ -25,7 +25,7 @@ entry:
entryTypeDescription: Tipo entrada entryTypeDescription: Tipo entrada
invoiceAmount: Importe invoiceAmount: Importe
dated: Fecha dated: Fecha
inventoryEntry: Es inventario inventoryEntry: Es inventario
summary: summary:
commission: Comisión commission: Comisión
currency: Moneda currency: Moneda
@ -33,7 +33,8 @@ entry:
invoiceAmount: Importe invoiceAmount: Importe
ordered: Pedida ordered: Pedida
booked: Contabilizada booked: Contabilizada
excludedFromAvailable: Excluido excludedFromAvailable: Excluir del disponible
isConfirmed: Lista para etiquetar
travelReference: Referencia travelReference: Referencia
travelAgency: Agencia travelAgency: Agencia
travelShipped: F. envio travelShipped: F. envio
@ -56,7 +57,7 @@ entry:
observation: Observación observation: Observación
commission: Comisión commission: Comisión
booked: Contabilizada booked: Contabilizada
excludedFromAvailable: Excluido excludedFromAvailable: Excluir del disponible
initialTemperature: Ini °C initialTemperature: Ini °C
finalTemperature: Fin °C finalTemperature: Fin °C
buys: buys:
@ -119,9 +120,9 @@ entry:
supplierName: Proveedor supplierName: Proveedor
entryFilter: entryFilter:
params: params:
isExcludedFromAvailable: Excluido isExcludedFromAvailable: Excluir del disponible
isOrdered: Pedida isOrdered: Pedida
isConfirmed: Confirmado isConfirmed: Lista para etiquetar
isReceived: Recibida isReceived: Recibida
isRaid: Raid isRaid: Raid
landed: Fecha landed: Fecha

View File

@ -36,7 +36,7 @@ const warehousesOptionsIn = ref([]);
auto-load auto-load
:filter="{ where: { isDestiny: TRUE } }" :filter="{ where: { isDestiny: TRUE } }"
/> />
<FormModel :url-update="`Travels/${route.params.id}`" model="Travel" auto-load> <FormModel :url-update="`Travels/${route.params.id}`" model="Travel">
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow>
<VnInput v-model="data.ref" :label="t('globals.reference')" /> <VnInput v-model="data.ref" :label="t('globals.reference')" />
@ -57,8 +57,8 @@ const warehousesOptionsIn = ref([]);
<VnRow> <VnRow>
<VnInputDate <VnInputDate
v-model="data.availabled" v-model="data.availabled"
:label="t('travel.summary.availabled')" :label="t('travel.summary.availabled')"
/> />
<VnInputTime <VnInputTime
v-model="data.availabled" v-model="data.availabled"
:label="t('travel.summary.availabledHour')" :label="t('travel.summary.availabledHour')"
@ -96,6 +96,7 @@ const warehousesOptionsIn = ref([]);
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>
<VnInput v-model="data.awbFk" :label="t('travel.awbFk')" />
</VnRow> </VnRow>
<VnRow> <VnRow>
<QCheckbox v-model="data.isRaid" :label="t('travel.basicData.isRaid')" /> <QCheckbox v-model="data.isRaid" :label="t('travel.basicData.isRaid')" />

View File

@ -12,6 +12,7 @@ export default {
'isRaid', 'isRaid',
'daysInForward', 'daysInForward',
'availabled', 'availabled',
'awbFk',
], ],
include: [ include: [
{ {

View File

@ -80,6 +80,11 @@ describe('EntryBuys', () => {
checkColor('amount', COLORS.positive); checkColor('amount', COLORS.positive);
cy.saveCard(); cy.saveCard();
cy.get('tbody > tr [tabindex="0"][role="checkbox"]').click();
cy.dataCy('transferBuys').should('be.enabled').click();
cy.dataCy('entryDestinyInput').should('be.visible').type('100');
cy.dataCy('transferBuysBtn').click();
cy.deleteEntry(); cy.deleteEntry();
}); });