Merge branch 'master' into Hotfix-OpenFilesInsteadOfDownloading
gitea/salix-front/pipeline/pr-master This commit looks good Details

This commit is contained in:
Javier Segarra 2025-05-20 07:16:43 +00:00
commit a8f6a31cef
5 changed files with 72 additions and 52 deletions

View File

@ -56,26 +56,6 @@ const defaultColumnAttrs = {
sortable: false, sortable: false,
}; };
const emit = defineEmits(['onDialogClosed', 'itemReplaced']); const emit = defineEmits(['onDialogClosed', 'itemReplaced']);
const priceStatusClass = (proposalPrice) => {
const originalPrice = sale.value?.price;
if (
!originalPrice ||
!ticketConfig.value ||
typeof ticketConfig.value.lackAlertPrice !== 'number'
) {
return 'price-ok';
}
const priceIncreasePercentage =
((proposalPrice - originalPrice) / originalPrice) * 100;
return priceIncreasePercentage > ticketConfig.value.lackAlertPrice
? 'price-alert'
: 'price-ok';
};
const columns = computed(() => [ const columns = computed(() => [
{ {
...defaultColumnAttrs, ...defaultColumnAttrs,
@ -196,7 +176,6 @@ const columns = computed(() => [
{ {
title: t('Replace'), title: t('Replace'),
icon: 'change_circle', icon: 'change_circle',
show: (row) => isSelectionAvailable(row),
action: change, action: change,
isPrimary: true, isPrimary: true,
}, },
@ -204,11 +183,18 @@ const columns = computed(() => [
}, },
]); ]);
function extractMatchValues(obj) { const priceStatusClass = (proposalPrice) => {
return Object.keys(obj) const originalPrice = sale.value?.price;
.filter((key) => key.startsWith(MATCH)) const { lackAlertPrice: lackAlert } = ticketConfig.value;
.map((key) => parseInt(key.replace(MATCH, ''), 10)); if (!originalPrice || !ticketConfig.value || typeof lackAlert !== 'number') {
} return 'price-ok';
}
const percentage = ((proposalPrice - originalPrice) / originalPrice) * 100;
return percentage > lackAlert ? 'price-alert' : 'price-ok';
};
const gradientStyleClass = (row) => { const gradientStyleClass = (row) => {
let color = 'white'; let color = 'white';
const value = parseFloat(row); const value = parseFloat(row);
@ -226,28 +212,49 @@ const gradientStyleClass = (row) => {
} }
return color; return color;
}; };
const extractMatchValues = (obj) => {
return Object.keys(obj)
.filter((key) => key.startsWith(MATCH))
.map((key) => parseInt(key.replace(MATCH, ''), 10));
};
const statusConditionalValue = (row) => { const statusConditionalValue = (row) => {
const matches = extractMatchValues(row); const matches = extractMatchValues(row);
const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0); const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0);
return 100 * (value / matches.length); return 100 * (value / matches.length);
}; };
const isSelectionAvailable = (itemProposal) => { const canReplace = (itemProposal) => {
const { price2, available } = itemProposal; if (!canReplaceByPrice(itemProposal)) {
const salePrice = sale.value.price; return false;
const { lackAlertPrice } = ticketConfig.value;
const isPriceTooHigh = (100 * price2) / salePrice > lackAlertPrice;
if (isPriceTooHigh) {
return isPriceTooHigh;
} }
const hasEnoughQuantity = return canReplaceByQuantity(itemProposal);
(100 * available) / Math.abs($props.itemLack.lack) < lackAlertPrice; };
return hasEnoughQuantity; const differenceByPrice = ({ price2: proposalPrice }) => {
const { price: salePrice } = sale.value;
const percentage = ((proposalPrice - salePrice) / salePrice) * 100;
return percentage;
};
const canReplaceByPrice = (itemProposal) =>
differenceByPrice(itemProposal) < ticketConfig.value.lackAlertPrice;
const differenceByQuantity = ({ available }) => {
const { quantity: saleQuantity } = sale.value;
const percentage = ((saleQuantity - available) / available) * 100;
return percentage;
}; };
const canReplaceByQuantity = (itemProposal) =>
differenceByQuantity(itemProposal) < ticketConfig.value.lackAlertPrice;
async function change(itemSubstitution) { async function change(itemSubstitution) {
if (!isSelectionAvailable(itemSubstitution)) { if (!canReplaceByPrice(itemSubstitution)) {
notify(t('notAvailable'), 'warning'); notify(t('notAvailableByPrice'), 'warning');
return;
}
if (!canReplaceByQuantity(itemSubstitution)) {
notify(t('notAvailableByQuantity'), 'warning');
return; return;
} }
const { itemFk: substitutionFk } = itemSubstitution; const { itemFk: substitutionFk } = itemSubstitution;
@ -277,9 +284,7 @@ async function handleTicketConfig(data) {
} }
function filterRows(data) { function filterRows(data) {
const filteredRows = data.sort( const filteredRows = data.sort((a, b) => canReplace(b) - canReplace(a));
(a, b) => isSelectionAvailable(b) - isSelectionAvailable(a),
);
proposalTableRef.value.CrudModelRef.formData = filteredRows; proposalTableRef.value.CrudModelRef.formData = filteredRows;
} }
</script> </script>
@ -315,6 +320,7 @@ function filterRows(data) {
> >
<template #top-right> <template #top-right>
<QBtn <QBtn
:disable="false"
flat flat
class="q-mr-sm" class="q-mr-sm"
color="primary" color="primary"
@ -369,16 +375,19 @@ function filterRows(data) {
</template> </template>
<template #column-price2="{ row }"> <template #column-price2="{ row }">
<div class="flex column items-center content-center"> <div class="flex column items-center content-center">
<!-- Use class binding for tooltip background -->
<QTooltip :offset="[0, 5]" anchor="top middle" self="bottom middle"> <QTooltip :offset="[0, 5]" anchor="top middle" self="bottom middle">
<div>{{ $t('proposal.price2') }}: {{ toCurrency(row.price2) }}</div> <div>{{ $t('proposal.price2') }}: {{ toCurrency(row.price2) }}</div>
<div> <div>
{{ $t('proposal.itemOldPrice') }}: {{ $t('proposal.itemOldPrice') }}:{{
{{ toCurrency(sales[0]?.price) }} toCurrency(sales[0]?.price)
}}
</div> </div>
<div>{{ $t('%€') }}: {{ differenceByPrice(row) }}%</div>
</QTooltip> </QTooltip>
<VnStockValueDisplay :format="'currency'" :value="-row.price2 / 100" /> <VnStockValueDisplay
<!-- Use class binding for text color --> :format="'currency'"
:value="row.price2 - sales[0]?.price"
/>
<span :class="[priceStatusClass(row.price2)]">{{ <span :class="[priceStatusClass(row.price2)]">{{
toCurrency(row.price2) toCurrency(row.price2)
}}</span> }}</span>
@ -434,7 +443,12 @@ function filterRows(data) {
</style> </style>
<i18n> <i18n>
en: en:
notAvailable: 'Not available for replacement' notAvailable: Not available for replacement
notAvailableByPrice: Not available for replacement by price
notAvailableByQuantity: Not available for replacement by quantity
es: es:
notAvailable: 'No disponible para reemplazo' notAvailable: No disponible para reemplazo
notAvailableByPrice: No disponible para reemplazo por precio
notAvailableByQuantity: No disponible para reemplazo por cantidad
Replace: Remplazar
</i18n> </i18n>

View File

@ -26,7 +26,8 @@ const { notify } = useNotify();
const totalRows = ref({}); const totalRows = ref({});
const arrayData = useArrayData('SupplierConsumption', { const arrayData = useArrayData('SupplierConsumption', {
url: 'Suppliers/consumption', url: 'Suppliers/consumption',
order: ['itemTypeFk', 'itemName', 'itemSize'], order: ['shipped DESC', 'itemTypeFk', 'itemName', 'itemSize'],
limit: 0,
userFilter: { where: { supplierFk: route.params.id } }, userFilter: { where: { supplierFk: route.params.id } },
}); });
const headerColumns = computed(() => [ const headerColumns = computed(() => [

View File

@ -1,11 +1,13 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import split from './components/split'; import split from './components/split';
import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults'; import { displayResults } from 'src/pages/Ticket/Negative/composables/notifyResults';
const { notifyResults } = displayResults(); const { notifyResults } = displayResults();
const emit = defineEmits(['ticketTransferred']); const emit = defineEmits(['ticketTransferred']);
const { t } = useI18n();
const $props = defineProps({ const $props = defineProps({
ticket: { ticket: {
@ -27,7 +29,7 @@ const splitSelectedRows = async () => {
<template> <template>
<VnInputDate <VnInputDate
class="q-mr-sm" class="q-mr-sm"
:label="$t('New date')" :label="t('New date')"
v-model="splitDate" v-model="splitDate"
clearable clearable
autofocus autofocus
@ -41,6 +43,9 @@ const splitSelectedRows = async () => {
</style> </style>
<i18n> <i18n>
es: es:
New date: Nueva fecha
Split: Separar
Transfer lines: Transferir líneas
Sales to transfer: Líneas a transferir Sales to transfer: Líneas a transferir
Destination ticket: Ticket destinatario Destination ticket: Ticket destinatario
</i18n> </i18n>

View File

@ -1,5 +1,5 @@
import '../commands.js'; import '../commands.js';
describe('EntryBuys', () => { describe.skip('EntryBuys', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1920, 1080); cy.viewport(1920, 1080);
cy.login('buyer'); cy.login('buyer');

View File

@ -13,7 +13,7 @@ const clickNotificationAction = () => {
expect(firstArg).to.include(`/ticket/${ticketId}/sale`); expect(firstArg).to.include(`/ticket/${ticketId}/sale`);
}); });
}; };
describe('Ticket Lack detail', { testIsolation: true }, () => { describe.skip('Ticket Lack detail', { testIsolation: true }, () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1980, 1020); cy.viewport(1980, 1020);
cy.login('developer'); cy.login('developer');