Merge pull request 'feat: refs #8638 add AWB field to travel and entry forms, update translations and styles' (!1620) from 8638-entryUpgradesForInvoiceInFixes into test
gitea/salix-front/pipeline/head This commit looks good Details

Reviewed-on: #1620
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
Pablo Natek 2025-03-24 12:52:12 +00:00
commit 9f9f4555dc
15 changed files with 297 additions and 40 deletions

View File

@ -895,7 +895,7 @@ const rowCtrlClickFunction = computed(() => {
{{ row[splittedColumns.title.name] }}
</span>
</QCardSection>
<!-- Fields -->
<!-- Fields -->
<QCardSection
class="q-pl-sm q-py-xs"
:class="$props.cardClass"
@ -1154,7 +1154,7 @@ es:
.grid-create {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
max-width: 100%;
grid-gap: 20px;
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 ?? []),
(val) => {
const maxlength = $props.maxlength;
if (maxlength && +val.length > maxlength)
if (maxlength && +val?.length > maxlength)
return t(`maxLength`, { value: maxlength });
const { min, max } = vnInputRef.value.$attrs;
if (!min) return null;

View File

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

View File

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

View File

@ -899,6 +899,7 @@ travel:
search: Buscar envío
searchInfo: Buscar envío por id o nombre
id: Id
awbFk: Guía aérea
travelList:
tableVisibleColumns:
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 VnSelectSupplier from 'src/components/common/VnSelectSupplier.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 { t } = useI18n();
@ -24,6 +26,7 @@ const user = state.getUser().fn();
const companiesOptions = ref([]);
const currenciesOptions = ref([]);
const entryRef = ref({});
onMounted(() => {
checkEntryLock(route.params.id, user.id);
@ -48,10 +51,11 @@ onMounted(() => {
auto-load
/>
<FormModel
:url-update="`Entries/${route.params.id}`"
ref="entryRef"
model="Entry"
auto-load
:url-update="`Entries/${route.params.id}`"
:clear-store-on-unmount="false"
auto-load
>
<template #form="{ data }">
<VnRow class="q-py-sm">
@ -67,11 +71,18 @@ onMounted(() => {
/>
</VnRow>
<VnRow class="q-py-sm">
<VnInput v-model="data.reference" :label="t('globals.reference')" />
<VnInputNumber
v-model="data.invoiceAmount"
:label="t('entry.summary.invoiceAmount')"
:positive="false"
<VnInput
v-model="data.reference"
:label="t('entry.list.tableVisibleColumns.reference')"
/>
<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 class="q-py-sm">
@ -113,7 +124,6 @@ onMounted(() => {
name="initialTemperature"
:label="t('entry.basicData.initialTemperature')"
:step="0.5"
:decimal-places="2"
:positive="false"
/>
<VnInputNumber
@ -121,20 +131,21 @@ onMounted(() => {
name="finalTemperature"
:label="t('entry.basicData.finalTemperature')"
:step="0.5"
:decimal-places="2"
:positive="false"
/>
<VnSelect
v-model="data.typeFk"
url="entryTypes"
:fields="['code', 'description']"
option-value="code"
optionLabel="description"
sortBy="description"
/>
</VnRow>
<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')"
type="textarea"
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 VnRow from 'src/components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const $props = defineProps({
id: {
@ -44,6 +45,8 @@ const entityId = ref($props.id ?? route.params.id);
const entryBuysRef = ref();
const footerFetchDataRef = ref();
const footer = ref({});
const dialogRef = ref(false);
const newEntryRef = ref(null);
const columns = [
{
align: 'center',
@ -250,6 +253,7 @@ const columns = [
component: 'number',
attrs: {
positive: false,
decimalPlaces: 3,
},
cellEvent: {
'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(() => {
stateStore.rightDrawer = false;
if ($props.editableMode) checkEntryLock(entityId.value, user.id);
@ -571,6 +592,47 @@ onMounted(() => {
</QItem>
</QList>
</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>
</Teleport>
<FetchData
@ -620,7 +682,7 @@ onMounted(() => {
},
columnGridStyle: {
'max-width': '50%',
'margin-right': '30px',
'margin-right': '5%',
flex: 1,
},
previousStyle: {
@ -816,6 +878,8 @@ es:
Create buy: Crear compra
Invert quantity value: Invertir valor de cantidad
Check buy amount: Marcar como correcta la cantidad de compra
Transfer buys: Transferir compras
Entry: Entrada
</i18n>
<style lang="scss" scoped>
.centered-container {

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@ const warehousesOptionsIn = ref([]);
auto-load
: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 }">
<VnRow>
<VnInput v-model="data.ref" :label="t('globals.reference')" />
@ -57,8 +57,8 @@ const warehousesOptionsIn = ref([]);
<VnRow>
<VnInputDate
v-model="data.availabled"
:label="t('travel.summary.availabled')"
/>
:label="t('travel.summary.availabled')"
/>
<VnInputTime
v-model="data.availabled"
:label="t('travel.summary.availabledHour')"
@ -96,6 +96,7 @@ const warehousesOptionsIn = ref([]);
</QIcon>
</template>
</VnInput>
<VnInput v-model="data.awbFk" :label="t('travel.awbFk')" />
</VnRow>
<VnRow>
<QCheckbox v-model="data.isRaid" :label="t('travel.basicData.isRaid')" />

View File

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

View File

@ -80,6 +80,11 @@ describe('EntryBuys', () => {
checkColor('amount', COLORS.positive);
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();
});