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

This commit is contained in:
Alex Moreno 2025-05-20 09:31:38 +00:00
commit 284e1a2ae2
16 changed files with 161 additions and 80 deletions

View File

@ -7,7 +7,6 @@ import axios from 'axios';
import { usePrintService } from 'composables/usePrintService';
import VnUserLink from '../ui/VnUserLink.vue';
import { downloadFile } from 'src/composables/downloadFile';
import VnImg from 'components/ui/VnImg.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnDms from 'src/components/common/VnDms.vue';

View File

@ -3,12 +3,13 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { toCurrency, toDateHourMin } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
import VnTable from 'components/VnTable/VnTable.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
const { t } = useI18n();
const route = useRoute();
const arrayData = useArrayData('Customer');
const filter = computed(() => {
return {
include: [
@ -77,7 +78,10 @@ const columns = computed(() => [
:create="{
urlUpdate: `Clients/${route.params.id}`,
title: t('New credit'),
onDataSaved: () => tableRef.reload(),
onDataSaved: () => {
arrayData.fetch({ append: false });
tableRef.reload();
},
formInitialData: { credit: tableData.at(0)?.amount },
}"
>

View File

@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router';
import { QBadge, QBtn, QCheckbox } from 'quasar';
import { downloadFile } from 'src/composables/downloadFile';
import { usePrintService } from 'composables/usePrintService';
import { toDateTimeFormat } from 'src/filters/date';
import FetchData from 'components/FetchData.vue';
@ -15,7 +15,7 @@ import CustomerFileManagementActions from 'src/pages/Customer/components/Custome
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const { openReport } = usePrintService();
const ClientDmsRef = ref(null);
const rows = ref([]);
@ -87,7 +87,7 @@ const tableColumnComponents = {
file: {
component: QBtn,
props: () => ({ flat: true }),
event: ({ row }) => downloadFile(row.dmsFk),
event: ({ row }) => openReport(`dms/${row.dmsFk}/downloadFile`, {}, '_blank'),
},
employee: {
component: QBtn,

View File

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

View File

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

View File

@ -187,11 +187,11 @@ const getRowUpdateInputEvents = (sale) => {
};
const resetChanges = async () => {
const _selectedRows = selectedRows.value;
await arrayData.fetch({ append: false });
tableRef.value.CrudModelRef.hasChanges = false;
await tableRef.value.reload();
selectedRows.value = [];
tableRef.value.selected = _selectedRows;
};
const changeQuantity = async (sale) => {
if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity)

View File

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

View File

@ -366,7 +366,11 @@ function openBalanceDialog(ticket) {
);
if (!isSameClient) {
throw new Error('You cannot make a payment on account from multiple clients');
notify(
t('You cannot make a payment on account from multiple clients'),
'negative',
);
return;
}
for (let ticketData of checkedTickets) {
@ -664,4 +668,5 @@ es:
Zone: Zona
New ticket: Nuevo ticket
Component lack: Faltan componentes
You cannot make a payment on account from multiple clients: No puedes hacer un pago a cuenta de varios clientes
</i18n>

View File

@ -52,30 +52,50 @@ const filterBanks = {
const state = useState();
const user = state.getUser();
const originalDescription = ref('');
const initialData = ref({
...$props.formData,
companyFk: user.value.companyFk,
payed: Date.vnNew(),
originalDescription: '',
});
function setPaymentType(data, accounting) {
data.bankFk = accounting.id;
if (!accounting) return;
data.bankFk = accounting.id;
accountingType.value = accounting.accountingType;
data.description = [];
data.payed = Date.vnNew();
isCash.value = accountingType.value.code == 'cash';
viewReceipt.value = isCash.value;
if (accountingType.value.daysInFuture)
data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture);
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
if (accountingType.value.code == 'compensation') return (data.description = '');
let descriptions = [];
if (accountingType.value.receiptDescription)
descriptions.push(accountingType.value.receiptDescription);
if (data.description > 0) descriptions.push(data.description);
data.description = descriptions.join(', ');
switch (accountingType.value.code) {
case 'compensation':
data.description.push($props.formData.description);
break;
default:
if (
accountingType.value.receiptDescription != null &&
accountingType.value.receiptDescription != ''
) {
data.description.push(accountingType.value.receiptDescription);
}
const originalDescription =
data.originalDescription || $props.formData.description;
if (originalDescription) {
data.description.push(originalDescription);
}
}
data.description = data.description.join(', ');
data.payed = Date.vnNew();
if (accountingType.value.daysInFuture) {
data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture);
}
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
}
const calculateFromAmount = (event) => {

View File

@ -1,5 +1,8 @@
/// <reference types="cypress" />
describe('Client credits', () => {
const credit = 100;
const descriptorCreditValue = '[data-cy="vnLvCredit"] > .value > span';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
@ -11,9 +14,19 @@ describe('Client credits', () => {
it('Should put a new credit', () => {
cy.get('.q-page').should('be.visible');
cy.dataCy('vnTableCreateBtn').click();
cy.dataCy('Credit_input').type('100');
cy.dataCy('Credit_input').type(credit);
cy.dataCy('FormModelPopup_save').click();
cy.checkNotification('Data saved');
cy.get(descriptorCreditValue)
.invoke('text')
.then((text) => {
const creditValue = parseInt(
text.trim().replace('€', '').split('.')[0],
10,
);
cy.log(creditValue);
expect(creditValue).to.equal(credit);
});
});
it('Should put a new credit with value 0 to close the client card', () => {

View File

@ -7,8 +7,8 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => {
});
Cypress.Commands.add('deleteEntry', () => {
cy.dataCy('descriptor-more-opts').should('be.visible').click();
cy.waitForElement('div[data-cy="delete-entry"]').click();
cy.openActionsDescriptor();
cy.get('[data-cy="delete-entry"]').click();
});
Cypress.Commands.add('createEntry', () => {

View File

@ -1,5 +1,5 @@
import '../commands.js';
describe('EntryBuys', () => {
describe.skip('EntryBuys', () => {
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('buyer');
@ -94,8 +94,10 @@ describe('EntryBuys', () => {
cy.get('button[data-cy="vnTableCreateBtn"]').click();
cy.get('input[data-cy="itemFk-create-popup"]').type('1');
cy.intercept('GET', /\/api\/Items\/1\/getVisibleAvailable/).as('item');
cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click();
cy.wait('@item');
cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing');
cy.get('button[data-cy="FormModelPopup_save"]').click();
cy.saveFormModel();
}
});

View File

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

View File

@ -6,7 +6,7 @@ describe('TicketBasicData', () => {
cy.visit('/#/ticket/31/basic-data');
});
it('Should redirect to customer basic data', () => {
it.skip('Should redirect to customer basic data', () => {
cy.get('.q-page').should('be.visible');
cy.get(':nth-child(2) > div > .text-primary').click();
cy.dataCy('Address_select').click();
@ -16,7 +16,7 @@ describe('TicketBasicData', () => {
).click();
cy.url().should('include', '/customer/1104/basic-data');
});
it.only('stepper', () => {
it('stepper', () => {
cy.get('.q-stepper__tab--active').should('have.class', 'q-stepper__tab--active');
cy.get('.q-stepper__nav > .q-btn--standard').click();

View File

@ -26,6 +26,24 @@ describe('TicketList', () => {
cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/);
});
it('should create payment ticket', () => {
cy.searchInFilterPanel().click();
const rowSelected =
'tbody > :nth-child(2) > :nth-child(1) > .q-checkbox > .q-checkbox__inner ';
cy.get(rowSelected).click();
cy.get(
'[style="transform: translate(-256px, 0px); margin: 140px 20px; z-index: 2;"] > div > .q-btn',
).click();
const description = 'Albaran: 31';
cy.dataCy('Reference_input').should('have.value', description);
cy.selectOption('[data-cy="paymentBank"]', 'Cash');
cy.dataCy('Delivered amount_input').clear().type('41.62');
cy.dataCy('Reference_input').should('have.value', `Cash, ${description}`);
cy.get('[aria-label="View recipt"]').click();
cy.get('.q-btn--standard > .q-btn__content > .block').click();
});
it('should open ticket summary', () => {
searchResults();
cy.getRow().find('.q-btn:last').click();

View File

@ -133,7 +133,7 @@ describe('TicketSale', { testIsolation: true }, () => {
cy.dataCy('recalculatePriceItem').click();
cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200);
cy.checkNotification('Data saved');
cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled');
cy.dataCy('ticketSaleMoreActionsDropdown').should('not.be.disabled');
});
it('should update discount when "Update discount" is clicked', () => {
@ -155,7 +155,7 @@ describe('TicketSale', { testIsolation: true }, () => {
cy.dataCy('saveManaBtn').click();
cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204);
cy.checkNotification('Data saved');
cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled');
cy.dataCy('ticketSaleMoreActionsDropdown').should('not.be.disabled');
});
it('adds claim', () => {