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

This commit is contained in:
PAU ROVIRA ROSALENY 2025-01-15 09:56:01 +01:00
commit 01b6e94008
190 changed files with 3415 additions and 2059 deletions

View File

@ -1,3 +1,246 @@
# Version 25.00 - 2025-01-14
### Added 🆕
- chore: refs #7056 move test by:jorgep
- feat: refs #7050 7050 add object check by:Jtubau
- feat: refs #7050 7050 add test to isEmpty() by:Jtubau
- feat: refs #7056 add tests in FormModel by:jorgep
- feat: refs #7056 update route meta information and add FormModel tests by:jorgep
- feat: refs #7074 tests for fns setData(), parseDms() and showFormDialog() (7074-makeFrontTestToVnDmsList) by:Jtubau
- feat: refs #7079 created VnLocation front test by:provira
- feat: refs #7189 add Accept-Language header to axios requests by:jorgep
- feat: refs #7924 add custom inspection checkbox and localization support by:jgallego
- feat: refs #7924 update custom inspection label for clarity in English and Spanish locales by:jgallego
- feat: refs #8004 enhance FetchedTags component with column support and styling updates by:pablone
- feat: refs #8004 hide rightFilter by:pablone
- feat: refs #8246 added new field in list by:Jon
- feat: refs #8266 added descriptor to item name by:jtubau
- feat: refs #8293 add zone filter by:Jtubau
- feat: refs #8293 include zone data in each record by:Jtubau
- fix: refs #8004 more list style issues by:pablone
- fix: refs #8004 some style issues on all list by:pablone
- style: refs #8004 update layout and styling in FetchedTags and ItemList components by:pablone
- style: update CustomerBalance.vue to set label color by:jgallego
### Changed 📦
- perf: order by:alexm
- perf: redirect transition list to card by:alexm
- perf: refs #8201 onDataSaved fetch by:Jon
- perf: revert processData by:alexm
- perf: simplify if by:alexm
- perf: simplify if (perf_redirectTransition) by:alexm
- refactor: item fixedPrice by:Jon
- refactor: refs #7050 refactorize by:jtubau
- refactor: refs #7050 removed blank spaces by:jtubau
- refactor: refs #7052 move EditTableCellValueForm tests to a new location and enhance test coverage by:jgallego
- refactor: refs #7052 remove unnecessary console logs from EditTableCellValueForm tests by:jgallego
- refactor: refs #7074 move dms constant to global scope by:Jtubau
- refactor: refs #7079 removed useless code by:provira
- refactor: refs #7924 simplify custom inspection icon rendering in ExtraCommunity.vue by:jgallego
- refactor: refs #8004 remove console log from CardSummary component on mount by:pablone
- refactor: refs #8004 remove consoleLogs by:pablone
- refactor: refs #8004 remove unused stateStore import in InvoiceInList.vue by:pablone
- refactor: refs #8004 remove unused travelFilterRef and chip definition in TravelList.vue by:pablone
- refactor: refs #8004 replace VnSelect with VnSelectWorker in CustomerList component by:pablone
- refactor: refs #8201 added onMounted to stablish the value to show icons by:Jon
- refactor: refs #8201 deleted condition by:Jon
- refactor: refs #8201 deleted log by:Jon
- refactor: refs #8201 deleted logs by:Jon
- refactor: refs #8266 8266 change expedition item name by:Jtubau
- refactor: refs #8266 change expedition label by:Jtubau
- refactor: refs #8293 remove redundant attributes by:Jtubau
- refactor: refs #8320 changed folder names from "specs" to "**tests**" by:provira
- refactor: refs #8320 moved front tests to their respective sections by:provira
- refactor: refs #8813 removed unused class property by:provira
### Fixed 🛠️
- fix: discount class by:PAU ROVIRA ROSALENY
- fix: duplicate transalation after test to dev by:alexm
- fix: fix translations by:carlossa
- fix: redirect to sales when confirming lines by:Jon
- fix: refs #7050 delete import added by mistake by:Jtubau
- fix: refs #7133 handleSalesModelValue function to handle empty input by:jorgep
- fix: refs #7189 update user language on sessionStorage by:jorgep
- fix: refs #7935 remove unused 'companyFk' column from InvoiceInList component by:jorgep
- fix: refs #8004 more list style issues by:pablone
- fix: refs #8004 some style issues on all list by:pablone
- fix: refs #8004 update label for daysOnward in TravelFilter component and add translations by:pablone
- fix: refs #8004 vnTable card with and add permanent labels by:pablone
- fix: refs #8201 added onDataSaved emi to refetch when cahnges are made by:Jon
- fix: refs #8201 use arrayData to fix the error by:Jon
- fix: refs #8314 space between label and value by:jtubau
- fix: refs #8813 fixed ClaimLines format by:provira
- fix: update button sizes in ExtraCommunity.vue for better visibility by:jgallego
- perf: revert processData by:alexm
- refactor: item fixedPrice by:Jon
# Version 24.52 - 2024-01-07
### Added 🆕
- chore: refs #8197 remove console log by:alexm
- chore: refs #8197 replace name by:alexm
- chore: refs #8197 unnecessary file by:alexm
- feat: #8110 apply mixin in quasar components by:Javier Segarra
- feat(Account & AccountRole): refs #8197 add VnCardMain by:alexm
- feat: addDptoLink by:Jtubau
- feat: added restore ticket function in ticket descriptor menu by:Jon
- feat: add support service wip by:jorgep
- feat: focus menu searchbar by:jorgep
- feat: make additional data object by:jorgep
- feat: message to grant access by:jorgep
- feat: refs #6583 add default param by:jorgep
- feat: refs #6583 add destination opt filter by:jorgep
- feat: refs #6583 add icon by:jorgep
- feat: refs #6583 add locale by:jorgep
- feat: refs #7072 added test to computed fn total by:Jtubau
- feat: refs #7235 update invoice out global form to fetch config based on serial type by:jgallego
- feat: refs #7301 add exclude inventory supplier from list by:pablone
- feat: refs #7301 enhance VnDateBadge styling and improve ItemLastEntries component by:pablone
- feat: refs #7882 Added distribution point by:guillermo
- feat: refs #7882 Added longitude & latitude by:guillermo
- feat: refs #7936 add autocomplete on tab fn by:jorgep
- feat: refs #7936 add company filter by:jorgep
- feat: refs #7936 add currency check before fetching by:jorgep
- feat: refs #7936 add dueDated field by:jorgep
- feat: refs #7936 add number validation to VnInputNumber & new daysAgo filter in InvoiceInFilter by:jorgep
- feat: refs #7936 add optionCaption by:jorgep
- feat: refs #7936 add row click navigation to InvoiceInSerial by:jorgep
- feat: refs #7936 add unit tests by:jorgep
- feat: refs #7936 add useAccountShortToStandard composable by:jorgep
- feat: refs #7936 calculate exchange & update taxable base by:jorgep
- feat: refs #7936 enhance downloadFile function to support opening in a new tab by:jorgep
- feat: refs #7936 enhance getTotal fn & add unit tests by:jorgep
- feat: refs #7936 enhance vn-select by:jorgep
- feat: refs #7936 improve optionLabel logic in InvoiceInVat component for better handling of numeric values by:jorgep
- feat: refs #7936 limit decimal places by:jorgep
- feat: refs #7936 make fields required by:jorgep
- feat: refs #7936 show country code & isVies fields by:jorgep
- feat: refs #7936 show id & value by:jorgep
- feat: refs #7936 simplify optionLabel wip by:jorgep
- feat: refs #7936 update 'isVies' label to use global translation key by:jorgep
- feat: refs #7936 update option labels in InvoiceIn components for better clarity by:jorgep
- feat: refs #7936 use default invoice data by:jorgep
- feat: refs #8001 change request by:robert
- feat: refs #8001 ticketExpeditionGrafana by:robert
- feat: refs #8194 created VnSelectWorker component and use it in Lilium by:Jon
- feat: refs #8197 better leftMenu and VnCardMain improvements by:alexm
- feat: refs #8197 default leftMenu by:alexm
- feat: refs #8197 default sectionName by:alexm
- feat: refs #8197 keepData in VnSection by:alexm
- feat: refs #8197 vnTableFilter by:alexm
- feat: refs #8197 working rightMenu by:alexm
- feat: remove re-fetch when add element by:Javier Segarra
- feat: remove search after category by:Javier Segarra
- feat: requested changes in item module by:Jon
- feat: update quantity by:Javier Segarra
- feat(VnPaginate): refs #8197 hold data when change to Card by:alexm
### Changed 📦
- perf: #6896 REMOVE COMMENTS by:Javier Segarra
- perf: qFormMixin by:Javier Segarra
- perf: qFormMixin improvement by:Javier Segarra
- perf: refs #8194 select worker component by:Jon
- perf: refs #8197 perf by:alexm
- perf: remove comments by:Javier Segarra
- perf: remove unused variables (origin/warmfix_noUsedVars) by:Javier Segarra
- refactor: added again search emit by:Jon
- refactor: add useCau composable by:jorgep
- refactor: deleted log by:Jon
- refactor: deleted onUnmounted code by:Jon
- refactor: deleted useless hidden tag by:Jon
- refactor: deleted warnings and corrected itemTag by:Jon
- refactor: drop logic by:jorgep
- refactor: ignore params when searching by id on searchbar (origin/VnSearchbar-SearchRemoveParams) by:Jon
- refactor: log error by:Jon
- refactor: refs #7936 locale by:jorgep
- refactor: refs #7936 simplify getTotal fn by:jorgep
- refactor: refs #7936 update label capitalization and replace invoice type options by:jorgep
- refactor: refs #8194 deleted unnecessary label by:Jon
- refactor: refs #8194 modified select worker template by:Jon
- refactor: refs #8194 modified select worker to allow no one filter from monitor ticket by:Jon
- refactor: refs #8194 moved translation to the correct place by:Jon
- refactor: refs #8194 requested changes by:Jon
- refactor: refs #8194 structure changes in component and related files by:Jon
- refactor: refs #8197 adapt AccountAcls to VnCardMain by:alexm
- refactor: refs #8197 adapt AccountAlias by:alexm
- refactor: refs #8197 adapt Ticket to VnCardMain by:alexm
- refactor: refs #8197 backward compatible (8197-VnCardMain_backwardCompatibility) by:alexm
- refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection by:alexm
- refactor: refs #8288 changed invoice out spanish translation by:provira
- refactor: use locale keys by:jorgep
- refactor: use teleport to avoid qdrawer overlapping by:Jon
- refactor: use VnSelectWorker by:Jon
### Fixed 🛠️
- fix: account by:carlossa
- fix: account create by:carlossa
- fix: accountList create by:carlossa
- fix(AccountList): use $refs by:alexm
- fix: add data-key by:alexm
- fix: addLocales by:Jtubau
- fix: dated field by:Jon
- fix: e2e by:jorgep
- fix: fix department filter by:carlossa
- fix: fixed translations by:Javier Segarra
- fix: fixed translations by:provira
- fix: get total from api by:Javier Segarra
- fix: handle non-object options by:jorgep
- fix: monitorPayMethodFilter by:carlossa
- fix: orderBy priority by:Javier Segarra
- fix: prevent null by:jorgep
- fix: redirection vnTable VnTableFilter by:alexm
- fix: refs #6389 fix filter trad by:carlossa
- fix: refs #6389 fix front, filters, itp by:carlossa
- fix: refs #6389 front add packing filter by:carlossa
- fix: refs #6389 front by:carlossa
- fix: refs #6389 front filters by:carlossa
- fix: refs #6389 ipt by:carlossa
- fix: refs #6389 packing by:carlossa
- fix: refs #6583 update checkbox for filtering by destination in TicketAdvanceFilter by:jorgep
- fix: refs #7031 add test e2e by:carlossa
- fix: refs #7031 fix zoneTest by:carlossa
- fix: refs #7301 unnecessary console logs from ItemLastEntries.vue by:pablone
- fix: refs #7936 changes by:jorgep
- fix: refs #7936 decimal places & locale by:jorgep
- fix: refs #7936 descriptor & dueday by:jorgep
- fix: refs #7936 exclude disabled els on tab by:jorgep
- fix: refs #7936 format tax calculation to two decimal places by:jorgep
- fix: refs #7936 improve error handling by:jorgep
- fix: refs #7936 redirection by:jorgep
- fix: refs #7936 rollback by:jorgep
- fix: refs #7936 serial by:jorgep
- fix: refs #7936 tabulation wip by:jorgep
- fix: refs #7936 test by:jorgep
- fix: refs #8114 clean by:carlossa
- fix: refs #8114 fix agencyList by:carlossa
- fix: refs #8114 fix lifeCycle hooks by:carlossa
- fix: refs #8114 fix pr by:carlossa
- fix: refs #8114 fix removeAddress by:carlossa
- fix: refs #8114 orderList by:carlossa
- fix: refs #8114 remove logs by:carlossa
- fix: refs #8197 mapKey (origin/8197-perf_vnTableInside, 8197-perf_vnTableInside) by:alexm
- fix: refs #8197 redirection (8197-perf_redirection) by:alexm
- fix: refs #8197 staticParams and redirect by:alexm
- fix: refs #8197 vnPaginate onFetch emit by:alexm
- fix: refs #8197 vnPaginate when change :id by:alexm
- fix: refs #8197 vnTableFilter in vnTable by:alexm
- fix: refs #8315 ticketBoxing test by:alexm
- fix: remove url by:carlossa
- fix: rollback by:jorgep
- fix: test by:jorgep
- fix(VnDmsList): refs #8197 add mapKey by:alexm
- revert: refs #8197 arrayData changes by:alexm
- test: refs #8197 fix e2e by:alexm
- test: refs #8315 fix claimDevelopment fixtures by:alexm
- test: refs #8315 fix clientList by:alexm
- test: refs #8315 fix VnSelect in e2e by:alexm
# Version 24.50 - 2024-12-10
### Added 🆕

View File

@ -11,6 +11,7 @@ module.exports = defineConfig({
screenshotsFolder: 'test/cypress/screenshots',
supportFile: 'test/cypress/support/index.js',
videosFolder: 'test/cypress/videos',
downloadsFolder: 'test/cypress/downloads',
video: false,
specPattern: 'test/cypress/integration/**/*.spec.js',
experimentalRunAllSpecs: true,

View File

@ -1,6 +1,6 @@
{
"name": "salix-front",
"version": "25.02.0",
"version": "25.04.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
@ -25,6 +25,7 @@
"axios": "^1.4.0",
"chromium": "^3.0.3",
"croppie": "^2.6.5",
"moment": "^2.30.1",
"pinia": "^2.1.3",
"quasar": "^2.14.5",
"validator": "^13.9.0",

View File

@ -20,6 +20,9 @@ dependencies:
croppie:
specifier: ^2.6.5
version: 2.6.5
moment:
specifier: ^2.30.1
version: 2.30.1
pinia:
specifier: ^2.1.3
version: 2.1.7(typescript@5.5.4)(vue@3.4.19)
@ -832,8 +835,8 @@ packages:
vue-i18n:
optional: true
dependencies:
'@intlify/message-compiler': 10.0.0
'@intlify/shared': 10.0.0
'@intlify/message-compiler': 11.0.0-rc.1
'@intlify/shared': 11.0.0-rc.1
jsonc-eslint-parser: 1.4.1
source-map: 0.6.1
vue-i18n: 9.9.1(vue@3.4.19)
@ -847,11 +850,11 @@ packages:
'@intlify/message-compiler': 9.9.1
'@intlify/shared': 9.9.1
/@intlify/message-compiler@10.0.0:
resolution: {integrity: sha512-OcaWc63NC/9p1cMdgoNKBj4d61BH8sUW1Hfs6YijTd9656ZR4rNqXAlRnBrfS5ABq0vjQjpa8VnyvH9hK49yBw==}
/@intlify/message-compiler@11.0.0-rc.1:
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 10.0.0
'@intlify/shared': 11.0.0-rc.1
source-map-js: 1.0.2
dev: true
@ -862,8 +865,8 @@ packages:
'@intlify/shared': 9.9.1
source-map-js: 1.0.2
/@intlify/shared@10.0.0:
resolution: {integrity: sha512-6ngLfI7DOTew2dcF9WMJx+NnMWghMBhIiHbGg+wRvngpzD5KZJZiJVuzMsUQE1a5YebEmtpTEfUrDp/NqVGdiw==}
/@intlify/shared@11.0.0-rc.1:
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
engines: {node: '>= 16'}
dev: true
@ -887,7 +890,7 @@ packages:
optional: true
dependencies:
'@intlify/bundle-utils': 4.0.0(vue-i18n@9.9.1)
'@intlify/shared': 10.0.0
'@intlify/shared': 11.0.0-rc.1
'@rollup/pluginutils': 4.2.1
'@vue/compiler-sfc': 3.4.19
debug: 4.3.4(supports-color@8.1.1)
@ -4960,6 +4963,10 @@ packages:
uuid: 8.3.2
dev: true
/moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
dev: false
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}

View File

@ -31,7 +31,7 @@ export default {
console.error(error);
}
form.addEventListener('keyup', function (evt) {
if (evt.key === 'Enter') {
if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) {
const input = evt.target;
if (input.type == 'textarea' && evt.shiftKey) {
evt.preventDefault();

View File

@ -62,6 +62,7 @@ async function setProvince(id, data) {
postcodeFormData.provinceFk = id;
await fetchTowns();
}
async function onProvinceCreated(data) {
postcodeFormData.provinceFk = data.id;
}

View File

@ -198,6 +198,7 @@ async function fetch() {
} catch (e) {
state.set(modelValue, {});
originalData.value = {};
throw e;
}
}

View File

@ -44,7 +44,7 @@ const onDataSaved = (data) => {
<FormModelPopup
url-create="Items/regularize"
model="Items"
:title="t('Regularize stock')"
:title="t('item.regularizeStock')"
:form-initial-data="regularizeFormData"
@on-data-saved="onDataSaved($event)"
>
@ -55,6 +55,7 @@ const onDataSaved = (data) => {
v-model.number="data.quantity"
type="number"
autofocus
data-cy="regularizeStockInput"
/>
</VnRow>
<VnRow>

View File

@ -314,7 +314,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
show-if-above
>
<QScrollArea class="fit">
<VnTableFilter :data-key="$attrs['data-key']" :columns="columns" />
<VnTableFilter :data-key="$attrs['data-key']" :columns="columns" :redirect="redirect" />
</QScrollArea>
</QDrawer>
<CrudModel

View File

@ -1,6 +1,5 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnFilter from 'components/VnTable/VnFilter.vue';
@ -11,16 +10,11 @@ defineProps({
type: Array,
required: true,
},
chipLocale: {
type: String,
default: null,
},
searchUrl: {
type: [String, Boolean],
default: 'table',
},
});
const { t } = useI18n();
const tableFilterRef = ref([]);
@ -62,9 +56,9 @@ function columnName(col) {
:columns="columns"
/>
</template>
<template #tags="{ tag, formatFn }" v-if="chipLocale">
<template #tags="{ tag, formatFn, getLocale }">
<div class="q-gutter-x-xs">
<strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong>
<strong>{{ getLocale(`${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>

View File

@ -0,0 +1,82 @@
import { createWrapper, axios } from 'app/test/vitest/helper';
import FilterItemForm from 'src/components/FilterItemForm.vue';
import { vi, beforeAll, describe, expect, it } from 'vitest';
describe('FilterItemForm', () => {
let vm;
let wrapper;
beforeAll(() => {
wrapper = createWrapper(FilterItemForm, {
props: {
url: 'Items/withName',
},
});
vm = wrapper.vm;
wrapper = wrapper.wrapper;
vi.spyOn(axios, 'get').mockResolvedValue({
data: [
{
id: 999996,
name: 'bolas de madera',
size: 2,
inkFk: null,
producerFk: null,
},
],
});
});
it('should filter data and populate tableRows for table display', async () => {
vm.itemFilterParams.name = 'bolas de madera';
await vm.onSubmit();
const expectedFilter = {
include: [
{ relation: 'producer', scope: { fields: ['name'] } },
{ relation: 'ink', scope: { fields: ['name'] } },
],
where: {"name":{"like":"%bolas de madera%"}},
};
expect(axios.get).toHaveBeenCalledWith('Items/withName', {
params: { filter: JSON.stringify(expectedFilter) },
});
expect(vm.tableRows).toEqual([
{
id: 999996,
name: 'bolas de madera',
size: 2,
inkFk: null,
producerFk: null,
},
]);
});
it('should handle an empty itemFilterParams correctly', async () => {
vm.itemFilterParams.name = null;
vm.itemFilterParams = {};
await vm.onSubmit();
const expectedFilter = {
include: [
{ relation: 'producer', scope: { fields: ['name'] } },
{ relation: 'ink', scope: { fields: ['name'] } },
],
where: {},
};
expect(axios.get).toHaveBeenCalledWith('Items/withName', {
params: { filter: JSON.stringify(expectedFilter) },
});
});
it('should emit "itemSelected" with the correct id and close the form', () => {
vm.selectItem({ id: 12345 });
expect(wrapper.emitted('itemSelected')[0]).toEqual([12345]);
});
});

View File

@ -14,6 +14,7 @@ defineProps({
hide-dropdown-icon
focus-on-mount
@update:model-value="promise"
data-cy="vnBtnSelect_select"
/>
</QBtnDropdown>
</template>

View File

@ -2,9 +2,9 @@
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnRow from '../ui/VnRow.vue';
import VnInput from './VnInput.vue';
import FetchData from '../FetchData.vue';
import useNotify from 'src/composables/useNotify';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
const props = defineProps({
submitFn: { type: Function, default: () => {} },
@ -70,19 +70,19 @@ defineExpose({ show: () => changePassDialog.value.show() });
</QCardSection>
<QForm ref="form">
<QCardSection>
<VnInput
<VnInputPassword
v-if="props.askOldPass"
:label="t('Old password')"
v-model="passwords.oldPassword"
type="password"
:required="true"
:toggle-visibility="true"
autofocus
/>
<VnInput
<VnInputPassword
:label="t('New password')"
v-model="passwords.newPassword"
type="password"
:required="true"
:toggle-visibility="true"
:info="
t('passwordRequirements', {
length: requirements.length,
@ -95,10 +95,10 @@ defineExpose({ show: () => changePassDialog.value.show() });
autofocus
/>
<VnInput
<VnInputPassword
:label="t('Repeat password')"
v-model="passwords.repeatPassword"
type="password"
:toggle-visibility="true"
/>
</QCardSection>
</QForm>

View File

@ -11,9 +11,9 @@ function getBadgeAttrs(date) {
let timeDiff = today - timeTicket;
if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' };
if (timeDiff < 0) return { color: 'success', 'text-color': 'black' };
return { color: 'transparent', 'text-color': 'white' };
if (timeDiff == 0) return { color: 'warning', class: 'black-text-color' };
if (timeDiff < 0) return { color: 'success', class: 'black-text-color' };
return { color: 'transparent', class: 'normal-text-color' };
}
function formatShippedDate(date) {
@ -29,3 +29,11 @@ function formatShippedDate(date) {
{{ formatShippedDate(date) }}
</QBadge>
</template>
<style lang="scss">
.black-text-color {
color: var(--vn-black-text-color);
}
.normal-text-color {
color: var(--vn-text-color);
}
</style>

View File

@ -297,7 +297,7 @@ defineExpose({
ref="dmsRef"
:data-key="$props.model"
:url="$props.model"
:filter="dmsFilter"
:user-filter="dmsFilter"
:order="['dmsFk DESC']"
:auto-load="true"
@on-fetch="setData"

View File

@ -45,6 +45,7 @@ const $props = defineProps({
});
const vnInputRef = ref(null);
const showPassword = ref(false);
const value = computed({
get() {
return $props.modelValue;
@ -134,20 +135,23 @@ const handleInsertMode = (e) => {
hide-bottom-space
:data-cy="$attrs.dataCy ?? $attrs.label + '_input'"
>
<template v-if="$slots.prepend" #prepend>
<template #prepend>
<slot name="prepend" />
</template>
<template #append>
<QIcon
name="close"
size="xs"
v-if="
hover &&
value &&
!$attrs.disabled &&
!$attrs.readonly &&
$props.clearable
"
:style="{
visibility:
hover &&
value &&
!$attrs.disabled &&
!$attrs.readonly &&
$props.clearable
? 'visible'
: 'hidden',
}"
@click="
() => {
value = null;
@ -155,7 +159,7 @@ const handleInsertMode = (e) => {
emit('remove');
}
"
></QIcon>
/>
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
<QIcon v-if="info" name="info">
<QTooltip max-width="350px">
@ -166,18 +170,3 @@ const handleInsertMode = (e) => {
</QInput>
</div>
</template>
<i18n>
en:
inputMin: Must be more than {value}
maxLength: The value exceeds {value} characters
inputMax: Must be less than {value}
es:
inputMin: Debe ser mayor a {value}
maxLength: El valor excede los {value} carácteres
inputMax: Debe ser menor a {value}
</i18n>
<style lang="scss">
.q-field__append {
padding-inline: 0;
}
</style>

View File

@ -1,14 +1,12 @@
<script setup>
import { onMounted, watch, computed, ref, useAttrs } from 'vue';
import { date } from 'quasar';
import { useI18n } from 'vue-i18n';
import VnDate from './VnDate.vue';
import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const model = defineModel({ type: [String, Date] });
const { t } = useI18n();
const $props = defineProps({
isOutlined: {

View File

@ -0,0 +1,31 @@
<script setup>
import VnInput from 'src/components/common/VnInput.vue';
import { ref } from 'vue';
const model = defineModel({ type: [Number, String] });
const $props = defineProps({
toggleVisibility: {
type: Boolean,
default: false,
},
});
const showPassword = ref(false);
</script>
<template>
<VnInput
v-bind="{ ...$attrs }"
v-model="model"
:type="
$props.toggleVisibility ? (showPassword ? 'text' : 'password') : $attrs.type
"
>
<template #append v-if="toggleVisibility">
<QIcon
:name="showPassword ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showPassword = !showPassword"
/>
</template>
</VnInput>
</template>

View File

@ -2,7 +2,7 @@
import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import VnSelectDialog from 'components/common/VnSelectDialog.vue';
import { useI18n } from 'vue-i18n';
import { computed } from 'vue';
import { ref, watch } from 'vue';
import { useAttrs } from 'vue';
import { useRequired } from 'src/composables/useRequired';
const { t } = useI18n();
@ -16,6 +16,14 @@ const props = defineProps({
},
});
watch(
() => props.location,
(newValue) => {
if (!modelValue.value) return;
modelValue.value = formatLocation(newValue) ?? null;
}
);
const mixinRules = [requiredFieldRule];
const locationProperties = [
'postcode',
@ -43,9 +51,7 @@ const formatLocation = (obj, properties = locationProperties) => {
return filteredParts.join(', ');
};
const modelValue = computed(() =>
props.location ? formatLocation(props.location, locationProperties) : null
);
const modelValue = ref(props.location ? formatLocation(props.location) : null);
function showLabel(data) {
const dataProperties = [

View File

@ -20,16 +20,15 @@ const hasContent = ref();
let observer;
onMounted(() => {
if (teleportRef.value) {
const checkContent = () => {
hasContent.value = teleportRef.value.innerHTML.trim() !== '';
};
if (!teleportRef.value) return;
const checkContent = () => {
hasContent.value = teleportRef.value?.innerHTML?.trim() !== '';
};
observer = new MutationObserver(checkContent);
observer.observe(teleportRef.value, { childList: true, subtree: true });
observer = new MutationObserver(checkContent);
observer.observe(teleportRef.value, { childList: true, subtree: true });
checkContent();
}
checkContent();
});
</script>

View File

@ -4,11 +4,12 @@ import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnTableFilter from '../VnTable/VnTableFilter.vue';
import { onBeforeMount, computed } from 'vue';
import { useArrayData } from 'src/composables/useArrayData';
import { useRoute } from 'vue-router';
const $props = defineProps({
section: {
type: String,
required: true,
default: null,
},
dataKey: {
type: String,
@ -38,14 +39,28 @@ const $props = defineProps({
type: Boolean,
default: true,
},
keepData: {
type: Boolean,
default: true,
},
});
const sectionValue = computed(() => $props.section ?? $props.dataKey);
const route = useRoute();
let arrayData;
const sectionValue = computed(() => $props.section ?? $props.dataKey);
const isMainSection = computed(() => {
const isSame = sectionValue.value == route.name;
if (!isSame && arrayData) {
arrayData.reset(['userParams', 'userFilter']);
}
return isSame;
});
onBeforeMount(() => {
if ($props.dataKey)
arrayData = useArrayData($props.dataKey, {
searchUrl: 'table',
keepData: $props.keepData,
...$props.arrayDataProps,
navigate: $props.redirect,
});
@ -74,6 +89,6 @@ onBeforeMount(() => {
</slot>
</template>
</RightMenu>
<slot name="body" v-if="sectionValue == $route.name" />
<slot name="body" v-if="isMainSection" />
<RouterView v-else />
</template>

View File

@ -113,8 +113,15 @@ const $props = defineProps({
});
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const { optionLabel, optionValue, optionFilter, optionFilterValue, options, modelValue } =
toRefs($props);
const {
optionLabel,
optionValue,
optionCaption,
optionFilter,
optionFilterValue,
options,
modelValue,
} = toRefs($props);
const myOptions = ref([]);
const myOptionsOriginal = ref([]);
const vnSelectRef = ref();
@ -198,10 +205,10 @@ function filter(val, options) {
}
if (!row) return;
const id = row[$props.optionValue];
const id = String(row[$props.optionValue]);
const optionLabel = String(row[$props.optionLabel]).toLowerCase();
return id == search || optionLabel.includes(search);
return id.includes(search) || optionLabel.includes(search);
});
}
@ -321,6 +328,11 @@ function handleKeyDown(event) {
}
}
}
function getCaption(opt) {
if (optionCaption.value === false) return;
return opt[optionCaption.value] || opt[optionValue.value];
}
</script>
<template>
@ -391,8 +403,8 @@ function handleKeyDown(event) {
<QItemLabel>
{{ opt[optionLabel] }}
</QItemLabel>
<QItemLabel caption v-if="optionCaption !== false">
{{ `#${opt[optionCaption] || opt[optionValue]}` }}
<QItemLabel caption v-if="getCaption(opt)">
{{ `#${getCaption(opt)}` }}
</QItemLabel>
</QItemSection>
</QItem>

View File

@ -51,6 +51,7 @@ const url = computed(() => {
option-value="id"
option-label="nickname"
:fields="['id', 'name', 'nickname', 'code']"
:filter-options="['id', 'name', 'nickname', 'code']"
sort-by="nickname ASC"
>
<template #prepend v-if="$props.hasAvatar">
@ -71,7 +72,7 @@ const url = computed(() => {
{{ scope.opt.nickname }}
</QItemLabel>
<QItemLabel caption v-else>
{{ scope.opt.nickname }}, {{ scope.opt.code }}
#{{ scope.opt.id }}, {{ scope.opt.nickname }}, {{ scope.opt.code }}
</QItemLabel>
</QItemSection>
</QItem>

View File

@ -0,0 +1,72 @@
import { createWrapper } from 'app/test/vitest/helper.js';
import { describe, it, expect } from 'vitest';
import VnInputDate from 'components/common/VnInputDate.vue';
let vm;
let wrapper;
function generateWrapper(date, outlined, required) {
wrapper = createWrapper(VnInputDate, {
props: {
modelValue: date,
},
attrs: {
isOutlined: outlined,
required: required
},
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
};
describe('VnInputDate', () => {
describe('formattedDate', () => {
it('formats a valid date correctly', async () => {
generateWrapper('2023-12-25', false, false);
await vm.$nextTick();
expect(vm.formattedDate).toBe('25/12/2023');
});
it('updates the model value when a new date is set', async () => {
const input = wrapper.find('input');
await input.setValue('31/12/2023');
expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
expect(wrapper.emitted()['update:modelValue'][0][0]).toBe('2023-12-31T00:00:00.000Z');
});
it('should not update the model value when an invalid date is set', async () => {
const input = wrapper.find('input');
await input.setValue('invalid-date');
expect(wrapper.emitted()['update:modelValue'][0][0]).toBe('2023-12-31T00:00:00.000Z');
});
});
describe('styleAttrs', () => {
it('should return empty styleAttrs when isOutlined is false', async () => {
generateWrapper('2023-12-25', false, false);
await vm.$nextTick();
expect(vm.styleAttrs).toEqual({});
});
it('should set styleAttrs when isOutlined is true', async () => {
generateWrapper('2023-12-25', true, false);
await vm.$nextTick();
expect(vm.styleAttrs.outlined).toBe(true);
});
});
describe('required', () => {
it('should not applies required class when isRequired is false', async () => {
generateWrapper('2023-12-25', false, false);
await vm.$nextTick();
expect(wrapper.find('.vn-input-date').classes()).not.toContain('required');
});
it('should applies required class when isRequired is true', async () => {
generateWrapper('2023-12-25', false, true);
await vm.$nextTick();
expect(wrapper.find('.vn-input-date').classes()).toContain('required');
});
});
});

View File

@ -0,0 +1,95 @@
import { describe, it, expect } from 'vitest';
import VnJsonValue from 'src/components/common/VnJsonValue.vue';
import { createWrapper } from 'app/test/vitest/helper';
const buildComponent = (props) => {
return createWrapper(VnJsonValue, {
props,
}).wrapper;
};
describe('VnJsonValue', () => {
it('renders null value correctly', async () => {
const wrapper = buildComponent({ value: null });
const span = wrapper.find('span');
expect(span.text()).toBe('∅');
expect(span.classes()).toContain('json-null');
});
it('renders boolean true correctly', async () => {
const wrapper = buildComponent({ value: true });
const span = wrapper.find('span');
expect(span.text()).toBe('✓');
expect(span.classes()).toContain('json-true');
});
it('renders boolean false correctly', async () => {
const wrapper = buildComponent({ value: false });
const span = wrapper.find('span');
expect(span.text()).toBe('✗');
expect(span.classes()).toContain('json-false');
});
it('renders a short string correctly', async () => {
const wrapper = buildComponent({ value: 'Hello' });
const span = wrapper.find('span');
expect(span.text()).toBe('Hello');
expect(span.classes()).toContain('json-string');
});
it('renders a long string correctly with ellipsis', async () => {
const longString = 'a'.repeat(600);
const wrapper = buildComponent({ value: longString });
const span = wrapper.find('span');
expect(span.text()).toContain('...');
expect(span.text().length).toBeLessThanOrEqual(515);
expect(span.attributes('title')).toBe(longString);
expect(span.classes()).toContain('json-string');
});
it('renders a number correctly', async () => {
const wrapper = buildComponent({ value: 123.4567 });
const span = wrapper.find('span');
expect(span.text()).toBe('123.457');
expect(span.classes()).toContain('json-number');
});
it('renders an integer correctly', async () => {
const wrapper = buildComponent({ value: 42 });
const span = wrapper.find('span');
expect(span.text()).toBe('42');
expect(span.classes()).toContain('json-number');
});
it('renders a date correctly', async () => {
const date = new Date('2023-01-01');
const wrapper = buildComponent({ value: date });
const span = wrapper.find('span');
expect(span.text()).toBe('2023-01-01');
expect(span.classes()).toContain('json-object');
});
it('renders an object correctly', async () => {
const obj = { key: 'value' };
const wrapper = buildComponent({ value: obj });
const span = wrapper.find('span');
expect(span.text()).toBe(obj.toString());
expect(span.classes()).toContain('json-object');
});
it('renders an array correctly', async () => {
const arr = [1, 2, 3];
const wrapper = buildComponent({ value: arr });
const span = wrapper.find('span');
expect(span.text()).toBe(arr.toString());
expect(span.classes()).toContain('json-object');
});
it('updates value when prop changes', async () => {
const wrapper = buildComponent({ value: true });
await wrapper.setProps({ value: 123 });
const span = wrapper.find('span');
expect(span.text()).toBe('123');
expect(span.classes()).toContain('json-number');
});
});

View File

@ -0,0 +1,107 @@
import { describe, it, expect, vi, beforeAll, afterEach, beforeEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import VnNotes from 'src/components/ui/VnNotes.vue';
describe('VnNotes', () => {
let vm;
let wrapper;
let spyFetch;
let postMock;
let expectedBody;
const mockData= {name: 'Tony', lastName: 'Stark', text: 'Test Note', observationTypeFk: 1};
function generateExpectedBody() {
expectedBody = {...vm.$props.body, ...{ text: vm.newNote.text, observationTypeFk: vm.newNote.observationTypeFk }};
}
async function setTestParams(text, observationType, type){
vm.newNote.text = text;
vm.newNote.observationTypeFk = observationType;
wrapper.setProps({ selectType: type });
}
beforeAll(async () => {
vi.spyOn(axios, 'get').mockReturnValue({ data: [] });
wrapper = createWrapper(VnNotes, {
propsData: {
url: '/test',
body: { name: 'Tony', lastName: 'Stark' },
}
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
});
beforeEach(() => {
postMock = vi.spyOn(axios, 'post').mockResolvedValue(mockData);
spyFetch = vi.spyOn(vm.vnPaginateRef, 'fetch').mockImplementation(() => vi.fn());
});
afterEach(() => {
vi.clearAllMocks();
expectedBody = {};
});
describe('insert', () => {
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is null', async () => {
await setTestParams( null, null, true );
await vm.insert();
expect(postMock).not.toHaveBeenCalled();
expect(spyFetch).not.toHaveBeenCalled();
});
it('should not call axios.post and vnPaginateRef.fetch if newNote.text is empty', async () => {
await setTestParams( "", null, false );
await vm.insert();
expect(postMock).not.toHaveBeenCalled();
expect(spyFetch).not.toHaveBeenCalled();
});
it('should not call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is true', async () => {
await setTestParams( "Test Note", null, true );
await vm.insert();
expect(postMock).not.toHaveBeenCalled();
expect(spyFetch).not.toHaveBeenCalled();
});
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is missing and selectType is false', async () => {
await setTestParams( "Test Note", null, false );
generateExpectedBody();
await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
expect(spyFetch).toHaveBeenCalled();
});
it('should call axios.post and vnPaginateRef.fetch if observationTypeFk is setted and selectType is false', async () => {
await setTestParams( "Test Note", 1, false );
generateExpectedBody();
await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
expect(spyFetch).toHaveBeenCalled();
});
it('should call axios.post and vnPaginateRef.fetch when newNote is valid', async () => {
await setTestParams( "Test Note", 1, true );
generateExpectedBody();
await vm.insert();
expect(postMock).toHaveBeenCalledWith(vm.$props.url, expectedBody);
expect(spyFetch).toHaveBeenCalled();
});
});
});

View File

@ -6,6 +6,7 @@ import { useArrayData } from 'composables/useArrayData';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useState } from 'src/composables/useState';
import { useRoute } from 'vue-router';
import VnMoreOptions from './VnMoreOptions.vue';
const $props = defineProps({
url: {
@ -47,7 +48,6 @@ let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
const menuRef = ref();
defineExpose({ getData });
onBeforeMount(async () => {
@ -159,25 +159,11 @@ const toModule = computed(() =>
</QTooltip>
</QBtn>
</RouterLink>
<QBtn
v-if="$slots.menu"
color="white"
dense
flat
icon="more_vert"
round
size="md"
data-cy="descriptor-more-opts"
>
<QTooltip>
{{ t('components.cardDescriptor.moreOptions') }}
</QTooltip>
<QMenu :ref="menuRef">
<QList>
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</QList>
</QMenu>
</QBtn>
<VnMoreOptions v-if="$slots.menu">
<template #menu="{ menuRef }">
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</template>
</VnMoreOptions>
</div>
<slot name="before" />
<div class="body q-py-sm">
@ -222,8 +208,8 @@ const toModule = computed(() =>
/>
</template>
<style lang="scss" scoped>
:deep(.body) {
<style lang="scss">
.body {
background-color: var(--vn-section-color);
.text-h5 {
font-size: 20px;
@ -262,7 +248,9 @@ const toModule = computed(() =>
}
}
}
</style>
<style lang="scss" scoped>
.title {
overflow: hidden;
text-overflow: ellipsis;

View File

@ -1,10 +1,11 @@
<script setup>
import { ref, computed, watch, onBeforeMount, onMounted } from 'vue';
import { ref, computed, watch, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router';
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData';
import { isDialogOpened } from 'src/filters';
import { useStateStore } from 'src/stores/useStateStore';
import VnMoreOptions from './VnMoreOptions.vue';
const props = defineProps({
url: {
@ -40,7 +41,6 @@ const { store } = arrayData;
const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
const isLoading = ref(false);
const stateStore = useStateStore();
defineExpose({
entity,
fetch,
@ -52,9 +52,6 @@ onBeforeMount(async () => {
watch(props, async () => await fetch());
});
onMounted(() => {
stateStore.rightDrawerChangeValue(false);
});
async function fetch() {
store.url = props.url;
store.filter = props.filter ?? {};
@ -64,6 +61,7 @@ async function fetch() {
isLoading.value = false;
}
</script>
<template>
<div class="summary container">
<QCard class="cardSummary">
@ -86,9 +84,14 @@ async function fetch() {
<slot name="header" :entity="entity" dense>
{{ entity.id + ' - ' + entity.name }}
</slot>
<slot name="header-right" :entity="entity">
<span></span>
</slot>
<span class="row no-wrap">
<slot name="header-right" :entity="entity" />
<VnMoreOptions v-if="$slots.menu && isDialogOpened()">
<template #menu="{ menuRef }">
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</template>
</VnMoreOptions>
</span>
</div>
<div class="summaryBody row q-mb-md">
<slot name="body" :entity="entity" />
@ -97,6 +100,7 @@ async function fetch() {
</QCard>
</div>
</template>
<style lang="scss">
.summary.container {
display: flex;

View File

@ -190,7 +190,7 @@ const getLocale = (label) => {
const globalLocale = `globals.params.${param}`;
if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`)));
else return t(`${route.meta.moduleName}.params.${param}`);
else return t(`${route.meta.moduleName.toLowerCase()}.params.${param}`);
};
</script>

View File

@ -30,7 +30,7 @@ onBeforeMount(async () => {
.data;
if (!channel) channel = defaultChannel;
phone.value = await parsePhone(props.phoneNumber, props.country.toLowerCase());
phone.value = await parsePhone(props.phoneNumber, props.country?.toLowerCase());
config[
type
].url = `${url}?customerIdentity=%2B${phone.value}&channelId=${channel}`;

View File

@ -0,0 +1,20 @@
<template>
<QBtn
color="white"
dense
flat
icon="more_vert"
round
size="md"
data-cy="descriptor-more-opts"
>
<QTooltip>
{{ $t('components.cardDescriptor.moreOptions') }}
</QTooltip>
<QMenu ref="menuRef">
<QList>
<slot name="menu" :menu-ref="$refs.menuRef" />
</QList>
</QMenu>
</QBtn>
</template>

View File

@ -110,7 +110,7 @@ onBeforeRouteLeave((to, from, next) => {
:url="$props.url"
order="created DESC"
:limit="0"
:filter="$props.filter"
:user-filter="$props.filter"
auto-load
ref="vnPaginateRef"
class="show"

View File

@ -111,6 +111,7 @@ onMounted(async () => {
});
onBeforeUnmount(() => {
if (!store.keepData) arrayData.reset(['data']);
arrayData.resetPagination();
});

View File

@ -1,14 +1,16 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { onMounted, ref, computed, watch } from 'vue';
import { useQuasar } from 'quasar';
import { useArrayData } from 'composables/useArrayData';
import VnInput from 'src/components/common/VnInput.vue';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'src/stores/useStateStore';
import { useRoute } from 'vue-router';
const quasar = useQuasar();
const { t } = useI18n();
const state = useStateStore();
const route = useRoute();
const props = defineProps({
dataKey: {
@ -83,6 +85,17 @@ if (props.redirect)
};
let arrayData = useArrayData(props.dataKey, arrayDataProps);
let store = arrayData.store;
const to = computed(() => {
const url = { path: route.path, query: { ...(route.query ?? {}) } };
const searchUrl = arrayData.store.searchUrl;
const currentFilter = {
...arrayData.store.currentFilter,
search: searchText.value || undefined,
};
if (searchUrl) url.query[searchUrl] = JSON.stringify(currentFilter);
return url;
});
watch(
() => props.dataKey,
@ -126,28 +139,38 @@ async function search() {
delete filter.params.search;
}
await arrayData.applyFilter(filter);
searchText.value = undefined;
}
</script>
<template>
<Teleport to="#searchbar" v-if="state.isHeaderMounted()">
<QForm @submit="search" id="searchbarForm">
<RouterLink
:to="to"
@click="
!$event.shiftKey && !$event.ctrlKey && search();
$refs.input.focus();
"
>
<QIcon
v-if="!quasar.platform.is.mobile"
class="cursor-pointer"
name="search"
size="sm"
>
<QTooltip>{{ t('link') }}</QTooltip>
</QIcon>
</RouterLink>
<VnInput
id="searchbar"
ref="input"
v-model.trim="searchText"
:placeholder="t(props.label)"
dense
standout
autofocus
data-cy="vnSearchBar"
data-cy="vn-searchbar"
borderless
>
<template #prepend>
<QIcon
v-if="!quasar.platform.is.mobile"
class="cursor-pointer"
name="search"
@click="search"
/>
</template>
<template #append>
<QIcon
v-if="props.info && $q.screen.gt.xs"
@ -172,20 +195,52 @@ async function search() {
.q-field {
transition: width 0.36s;
}
</style>
<style lang="scss">
:deep(.q-field__native) {
padding-top: 10px;
padding-left: 5px;
}
:deep(.q-field--dark .q-field__native:focus) {
color: black;
}
:deep(.q-field--focused) {
.q-icon {
color: black;
}
}
.cursor-info {
cursor: help;
}
#searchbar {
.q-field--standout.q-field--highlighted .q-field__control {
.q-form {
display: flex;
align-items: center;
border-radius: 4px;
padding: 0 5px;
background-color: var(--vn-search-color);
&:hover {
background-color: var(--vn-search-color-hover);
}
&:focus-within {
background-color: white;
color: black;
.q-field__native,
.q-icon {
color: black !important;
color: black;
}
}
}
.q-icon {
color: var(--vn-label-color);
}
</style>
<i18n>
en:
link: click to search, ctrl + click to open in a new tab, shift + click to open in a new window
es:
link: clic para buscar, ctrl + clic para abrir en una nueva pestaña, shift + clic para abrir en una nueva ventana
</i18n>

View File

@ -0,0 +1,81 @@
import { describe, expect, it } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
describe('tags computed property', () => {
it('returns an object with the correct keys and values', () => {
const vm = createWrapper(FetchedTags, {
props: {
item: {
tag1: 'JavaScript',
value1: 'Programming Language',
tag2: 'Vue',
value2: 'Framework',
tag3: 'EmptyTag',
},
tag: 'tag',
value: 'value',
columns: 2,
},
}).vm;
expect(vm.tags).toEqual({
JavaScript: 'Programming Language',
Vue: 'Framework',
EmptyTag: '',
});
});
it('returns an empty object if the item prop is an empty object', () => {
const vm = createWrapper(FetchedTags, {
props: {
item: {},
tag: 'tag',
value: 'value',
},
}).vm;
expect(vm.tags).toEqual({});
});
it('should calculate the correct columnStyle when columns prop is defined', () => {
const vm = createWrapper(FetchedTags, {
props: {
item: {
tag1: 'JavaScript',
value1: 'Programming Language',
tag2: 'Vue',
value2: 'Framework',
tag3: 'EmptyTag',
},
tag: 'tag',
value: 'value',
columns: 2,
},
}).vm;
const expectedStyle = {
'grid-template-columns': 'repeat(2, 1fr)',
'max-width': '8rem',
};
expect(vm.columnStyle).toEqual(expectedStyle);
});
it('should return an empty object for columnStyle when columns prop is not defined', () => {
const vm = createWrapper(FetchedTags, {
props: {
item: {
tag1: 'JavaScript',
value1: 'Programming Language',
tag2: 'Vue',
value2: 'Framework',
tag3: 'EmptyTag',
},
tag: 'tag',
value: 'value',
columns: null,
},
}).vm;
expect(vm.columnStyle).toEqual({});
});
});

View File

@ -53,6 +53,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
'searchUrl',
'navigate',
'mapKey',
'keepData',
];
if (typeof userOptions === 'object') {
for (const option in userOptions) {
@ -75,26 +76,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
cancelRequest();
canceller = new AbortController();
const filter = {
limit: store.limit,
};
let userParams = { ...store.userParams };
Object.assign(filter, store.userFilter);
let where;
if (filter?.where || store.filter?.where)
where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
Object.assign(filter, store.filter);
filter.where = where;
const params = { filter };
Object.assign(params, userParams);
if (params.filter) params.filter.skip = store.skip;
if (store?.order && typeof store?.order == 'string') store.order = [store.order];
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order;
const { params, limit } = getCurrentFilter();
store.currentFilter = JSON.parse(JSON.stringify(params));
delete store.currentFilter.filter.include;
@ -120,15 +102,16 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
params,
});
const { limit } = filter;
store.hasMoreData = limit && response.data.length >= limit;
processData(response.data, { map: !!store.mapKey, append });
if (!append && !isDialogOpened()) updateRouter && updateStateParams();
if (!append && !isDialogOpened() && updateRouter) {
if (updateStateParams(response.data)?.redirect) return;
}
store.isLoading = false;
canceller = null;
processData(response.data, { map: !!store.mapKey, append });
return response;
}
@ -258,7 +241,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
if (Object.values(store.userParams).length) await fetch({});
}
function updateStateParams() {
function updateStateParams(data) {
if (!route?.path) return;
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
if (store?.searchUrl)
@ -275,21 +258,46 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
const { path } = matches.at(-1);
const to =
store?.data?.length === 1
? path.replace(/\/(list|:id)|-list/, `/${store.data[0].id}`)
data?.length === 1
? path.replace(/\/(list|:id)|-list/, `/${data[0].id}`)
: path.replace(/:id.*/, '');
if (route.path != to) {
const pushUrl = { path: to };
if (to.endsWith('/list') || to.endsWith('/'))
pushUrl.query = newUrl.query;
return router.push(pushUrl);
return router.push(pushUrl) && { redirect: true };
}
}
router.replace(newUrl);
}
function getCurrentFilter() {
const filter = {
limit: store.limit,
};
let userParams = { ...store.userParams };
Object.assign(filter, store.userFilter);
let where;
if (filter?.where || store.filter?.where)
where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
Object.assign(filter, store.filter);
filter.where = where;
const params = { filter };
Object.assign(params, userParams);
if (params.filter) params.filter.skip = store.skip;
if (store?.order && typeof store?.order == 'string') store.order = [store.order];
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order;
return { filter, params, limit: filter.limit };
}
function processData(data, { map = true, append = true }) {
if (!append) {
store.data = [];
@ -302,7 +310,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
for (const row of data) {
const key = row[store.mapKey];
const val = { ...row, key };
if (store.map.has(key)) {
if (key && store.map.has(key)) {
const { position } = store.map.get(key);
val.position = position;
store.map.set(key, val);
@ -322,6 +330,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
fetch,
applyFilter,
addFilter,
getCurrentFilter,
addFilterWhere,
addOrder,
deleteOrder,

View File

@ -0,0 +1,6 @@
import axios from 'axios';
export default async (id) => {
const { data } = await axios.get(`Accounts/${id}/exists`);
return data.exists;
};

View File

@ -3,20 +3,23 @@
@import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass';
body.body--light {
--font-color: black;
--vn-header-color: #cecece;
--vn-page-color: #ffffff;
--vn-section-color: #e0e0e0;
--vn-section-hover-color: #b9b9b9;
--vn-text-color: var(--font-color);
--vn-text-color: black;
--vn-label-color: #5f5f5f;
--vn-accent-color: #e7e3e3;
--vn-search-color: #d4d4d4;
--vn-search-color-hover: #cfcfcf;
--vn-empty-tag: #acacac;
--vn-black-text-color: black;
--vn-text-color-contrast: white;
background-color: var(--vn-page-color);
.q-header .q-toolbar {
color: var(--font-color);
color: var(--vn-text-color);
}
}
body.body--dark {
@ -27,7 +30,11 @@ body.body--dark {
--vn-text-color: white;
--vn-label-color: #a8a8a8;
--vn-accent-color: #424242;
--vn-search-color: #4b4b4b;
--vn-search-color-hover: #5b5b5b;
--vn-empty-tag: #2d2d2d;
--vn-black-text-color: black;
--vn-text-color-contrast: black;
background-color: var(--vn-page-color);
}
@ -86,6 +93,10 @@ select:-webkit-autofill {
background-color: var(--vn-section-hover-color);
}
.bg-vn-page {
background-color: var(--vn-page-color);
}
.color-vn-label {
color: var(--vn-label-color);
}
@ -189,7 +200,7 @@ select:-webkit-autofill {
.q-tooltip {
background-color: var(--vn-page-color);
color: var(--font-color);
color: var(--vn-text-color);
font-size: medium;
}

View File

@ -1,5 +1,6 @@
export default function toDateHourMin(date) {
const dateHour = new Date(date).toLocaleDateString('es-ES', {
if (!date) return date;
return new Date(date).toLocaleDateString('es-ES', {
timeZone: 'Europe/Madrid',
year: 'numeric',
month: '2-digit',
@ -7,5 +8,4 @@ export default function toDateHourMin(date) {
hour: '2-digit',
minute: '2-digit',
});
return dateHour;
}

View File

@ -1,5 +1,6 @@
export default function toDateHourMinSec(date) {
const dateHour = new Date(date).toLocaleDateString('es-ES', {
if (!date) return date;
return new Date(date).toLocaleDateString('es-ES', {
timeZone: 'Europe/Madrid',
year: 'numeric',
month: '2-digit',
@ -8,5 +9,4 @@ export default function toDateHourMinSec(date) {
minute: '2-digit',
second: '2-digit',
});
return dateHour;
}

View File

@ -455,7 +455,26 @@ entry:
packingOut: Package out
landing: Landing
isExcludedFromAvailable: Es inventory
params:
toShipped: To
fromShipped: From
warehouseiNFk: Warehouse
daysOnward: Days onward
daysAgo: Days ago
warehouseInFk: Warehouse in
ticket:
params:
ticketFk: Ticket ID
weekDay: Weekday
agencyModeFk: Agency
id: Worker
state: State
created: Created
externalId: External ID
counter: Counter
freightItemName: Freight item name
packageItemName: Package item name
longName: Long name
card:
customerId: Customer ID
customerCard: Customer card
@ -506,6 +525,7 @@ invoiceOut:
card:
issued: Issued
customerCard: Customer card
ticketList: Ticket List
summary:
issued: Issued
dued: Due
@ -697,6 +717,11 @@ wagon:
minHeightBetweenTrays: 'The minimum height between trays is '
maxWagonHeight: 'The maximum height of the wagon is '
uncompleteTrays: There are incomplete trays
params:
label: Label
plate: Plate
volume: Volume
name: Name
supplier:
list:

View File

@ -456,7 +456,25 @@ entry:
packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Es inventario
params:
toShipped: Hasta
fromShipped: Desde
warehouseInFk: Alm. entrada
daysOnward: Días adelante
daysAgo: Días atras
ticket:
params:
ticketFk: ID de ticket
weekDay: Salida
agencyModeFk: Agencia
id: Comercial
created: Creado
state: Estado
externalId: ID externo
counter: Contador
freightItemName: Nombre
packageItemName: Embalaje
longName: Descripción
card:
customerId: ID cliente
customerCard: Ficha del cliente
@ -700,6 +718,11 @@ wagon:
minHeightBetweenTrays: 'La distancia mínima entre bandejas es '
maxWagonHeight: 'La altura máxima del vagón es '
uncompleteTrays: Hay bandejas sin completar
params:
label: Etiqueta
plate: Matrícula
volume: Volumen
name: Nombre
supplier:
list:
payMethod: Método de pago

View File

@ -6,6 +6,7 @@ import FormModelPopup from 'components/FormModelPopup.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
const { t } = useI18n();
const router = useRouter();
@ -61,10 +62,10 @@ const redirectToAccountBasicData = (_, { id }) => {
hide-selected
:rules="validate('VnUser.roleFk')"
/>
<VnInput
<VnInputPassword
v-model="data.password"
:label="t('ldap.password')"
type="password"
:toggle-visibility="true"
:rules="validate('VnUser.password')"
/>
<QCheckbox

View File

@ -8,6 +8,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useArrayData } from 'src/composables/useArrayData';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
const { t } = useI18n();
const { notify } = useNotify();
@ -128,10 +129,9 @@ onMounted(async () => await getInitialLdapConfig());
:required="true"
:rules="validate('LdapConfig.rdn')"
/>
<VnInput
<VnInputPassword
:label="t('ldap.password')"
clearable
type="password"
v-model="data.password"
:required="true"
:rules="validate('LdapConfig.password')"

View File

@ -4,9 +4,9 @@ import { computed, ref } from 'vue';
import VnTable from 'components/VnTable/VnTable.vue';
import AccountSummary from './Card/AccountSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import VnInput from 'src/components/common/VnInput.vue';
import VnSection from 'src/components/common/VnSection.vue';
import FetchData from 'src/components/FetchData.vue';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
@ -168,10 +168,9 @@ function exprBuilder(param, value) {
>
<template #more-create-dialog="{ data }">
<QCardSection>
<VnInput
<VnInputPassword
:label="t('Password')"
v-model="data.password"
type="password"
:required="true"
autocomplete="new-password"
/>

View File

@ -8,6 +8,7 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useArrayData } from 'src/composables/useArrayData';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
const { t } = useI18n();
const { notify } = useNotify();
@ -143,10 +144,9 @@ onMounted(async () => await getInitialSambaConfig());
v-model="data.adUser"
:rules="validate('SambaConfigs.adUser')"
/>
<VnInput
<VnInputPassword
:label="t('samba.passwordAD')"
clearable
type="password"
v-model="data.adPassword"
/>
<VnInput

View File

@ -1,13 +1,13 @@
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
import FetchData from 'src/components/FetchData.vue';
import VnImg from 'src/components/ui/VnImg.vue';
import useHasAccount from 'src/composables/useHasAccount.js';
const $props = defineProps({
id: {
@ -23,6 +23,7 @@ const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const hasAccount = ref();
const setData = (entity) => (data.value = useCardDescription(entity.nickname, entity.id));
const filter = {
@ -30,18 +31,16 @@ const filter = {
fields: ['id', 'nickname', 'name', 'role'],
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
};
const hasAccount = ref(false);
onMounted(async () => {
hasAccount.value = await useHasAccount(entityId.value);
});
</script>
<template>
<FetchData
:url="`Accounts/${entityId}/exists`"
auto-load
@on-fetch="(data) => (hasAccount = data.exists)"
/>
<CardDescriptor
ref="descriptor"
url="VnUsers/preview"
:url="`VnUsers/preview`"
:filter="filter"
module="Account"
@on-fetch="setData"
@ -50,7 +49,7 @@ const hasAccount = ref(false);
:subtitle="data.subtitle"
>
<template #menu>
<AccountDescriptorMenu :has-account="hasAccount" />
<AccountDescriptorMenu :entity-id="entityId" />
</template>
<template #before>
<VnImg :id="entityId" collection="user" resolution="520x520" class="photo">
@ -74,7 +73,7 @@ const hasAccount = ref(false);
<VnLv :label="t('account.card.nickname')" :value="entity.name" />
<VnLv :label="t('account.card.role')" :value="entity.role.name" />
</template>
<template #icons="{ entity }">
<template #actions="{ entity }">
<QCardActions class="q-gutter-x-md">
<QIcon
v-if="!entity.active"
@ -82,7 +81,7 @@ const hasAccount = ref(false);
name="vn:disabled"
flat
round
size="xs"
size="sm"
class="fill-icon"
>
<QTooltip>{{ t('account.card.deactivated') }}</QTooltip>
@ -93,7 +92,7 @@ const hasAccount = ref(false);
v-if="hasAccount"
flat
round
size="xs"
size="sm"
class="fill-icon"
>
<QTooltip>{{ t('account.card.enabled') }}</QTooltip>

View File

@ -1,36 +1,37 @@
<script setup>
import axios from 'axios';
import { computed, ref, toRefs } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useRoute } from 'vue-router';
import { useAcl } from 'src/composables/useAcl';
import { useArrayData } from 'src/composables/useArrayData';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnChangePassword from 'src/components/common/VnChangePassword.vue';
import { useQuasar } from 'quasar';
import useNotify from 'src/composables/useNotify.js';
import useHasAccount from 'src/composables/useHasAccount.js';
import VnInputPassword from 'src/components/common/VnInputPassword.vue';
const $props = defineProps({
hasAccount: {
type: Boolean,
default: false,
entityId: {
type: Number,
required: true,
},
});
const { t } = useI18n();
const { hasAccount } = toRefs($props);
const { openConfirmationModal } = useVnConfirm();
const route = useRoute();
const { notify } = useQuasar();
const { notify } = useNotify();
const account = computed(() => useArrayData('AccountId').store.data[0]);
account.value.hasAccount = hasAccount.value;
const entityId = computed(() => +route.params.id);
onMounted(async () => {
account.value.hasAccount = await useHasAccount($props.entityId);
});
async function updateStatusAccount(active) {
if (active) {
await axios.post(`Accounts`, { id: entityId.value });
await axios.post(`Accounts`, { id: $props.entityId });
} else {
await axios.delete(`Accounts/${entityId.value}`);
await axios.delete(`Accounts/${$props.entityId}`);
}
account.value.hasAccount = active;
@ -41,7 +42,7 @@ async function updateStatusAccount(active) {
});
}
async function updateStatusUser(active) {
await axios.patch(`VnUsers/${entityId.value}`, { active });
await axios.patch(`VnUsers/${$props.entityId}`, { active });
account.value.active = active;
const status = active ? 'activate' : 'deactivate';
notify({
@ -97,7 +98,7 @@ async function sync() {
<QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip>
</QIcon></QCheckbox
>
<QInput
<VnInputPassword
v-if="shouldSyncPassword"
:label="t('login.password')"
v-model="syncPassword"

View File

@ -9,6 +9,7 @@ import AccountMailAliasCreateForm from './AccountMailAliasCreateForm.vue';
import { useVnConfirm } from 'composables/useVnConfirm';
import { useArrayData } from 'composables/useArrayData';
import useNotify from 'src/composables/useNotify.js';
import useHasAccount from 'src/composables/useHasAccount.js';
import axios from 'axios';
const { t } = useI18n();
@ -50,16 +51,6 @@ const columns = computed(() => [
},
]);
const fetchAccountExistence = async () => {
try {
const { data } = await axios.get(`Accounts/${route.params.id}/exists`);
return data.exists;
} catch (error) {
console.error('Error fetching account existence', error);
return false;
}
};
const deleteMailAlias = async (row) => {
await axios.delete(`${urlPath}/${row.id}`);
fetchMailAliases();
@ -79,7 +70,7 @@ const fetchMailAliases = async () => {
const getAccountData = async (reload = true) => {
loading.value = true;
hasAccount.value = await fetchAccountExistence();
hasAccount.value = await useHasAccount(route.params.id);
if (!hasAccount.value) {
loading.value = false;
store.data = [];

View File

@ -9,6 +9,7 @@ import VnRow from 'components/ui/VnRow.vue';
import axios from 'axios';
import { useStateStore } from 'stores/useStateStore';
import useNotify from 'src/composables/useNotify.js';
import useHasAccount from 'src/composables/useHasAccount';
const { t } = useI18n();
const route = useRoute();
@ -30,23 +31,9 @@ const hasDataChanged = computed(
initialData.value.hasData !== hasData.value
);
const fetchAccountExistence = async () => {
try {
const { data } = await axios.get(`Accounts/${route.params.id}/exists`);
return data.exists;
} catch (error) {
console.error('Error fetching account existence', error);
return false;
}
};
const fetchMailForwards = async () => {
try {
const response = await axios.get(`MailForwards/${route.params.id}`);
return response.data;
} catch {
return null;
}
const response = await axios.get(`MailForwards/${route.params.id}`);
return response.data;
};
const deleteMailForward = async () => {
@ -72,7 +59,7 @@ const setInitialData = async () => {
loading.value = true;
initialData.value.account = route.params.id;
formData.value.account = route.params.id;
hasAccount.value = await fetchAccountExistence(route.params.id);
hasAccount.value = await useHasAccount(route.params.id);
if (!hasAccount.value) {
loading.value = false;
return;

View File

@ -7,6 +7,7 @@ import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData';
import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
const route = useRoute();
const { t } = useI18n();
@ -31,12 +32,14 @@ const filter = {
<template>
<CardSummary
data-key="AccountId"
ref="AccountSummary"
url="VnUsers/preview"
:filter="filter"
@on-fetch="(data) => (account = data)"
>
<template #header>{{ account.id }} - {{ account.nickname }}</template>
<template #menu="">
<AccountDescriptorMenu :entity-id="entityId" />
</template>
<template #body>
<QCard class="vn-one">
<QCardSection class="q-pa-none">

View File

@ -1,4 +1,18 @@
account:
params:
id: Id
name: Name
roleFk: Role
nickname: Nickname
password: Password
active: Active
search: Id
description: Description
alias: Alias
model: Model
principalId: Role
property: Property
accessType: Access
card:
nickname: User
role: Role

View File

@ -1,4 +1,20 @@
accessType: Acceso
property: Propiedad
account:
params:
id: Id
name: Nombre
roleFk: Rol
nickname: Nickname
password: Contraseña
active: Activo
search: Id
description: Descripción
alias: Alias
model: Modelo
principalId: Rol
property: Propiedad
accessType: Acceso
card:
nickname: Usuario
role: Rol

View File

@ -1,21 +1,13 @@
<script setup>
import VnCard from 'components/common/VnCard.vue';
import VnCardBeta from 'components/common/VnCardBeta.vue';
import ClaimDescriptor from './ClaimDescriptor.vue';
import ClaimFilter from '../ClaimFilter.vue';
import filter from './ClaimFilter.js';
</script>
<template>
<VnCard
data-key="Claim"
base-url="Claims"
:descriptor="ClaimDescriptor"
:filter-panel="ClaimFilter"
search-data-key="ClaimList"
<VnCardBeta
data-key="Claim"
base-url="Claims"
:descriptor="ClaimDescriptor"
:filter="filter"
:searchbar-props="{
url: 'Claims/filter',
label: 'Search claim',
info: 'You can search by claim id or customer name',
}"
/>
</template>

View File

@ -19,6 +19,7 @@ import ClaimNotes from 'src/pages/Claim/Card/ClaimNotes.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ClaimDescriptorMenu from './ClaimDescriptorMenu.vue';
const route = useRoute();
const router = useRouter();
@ -228,6 +229,9 @@ function claimUrl(section) {
</QList>
</QBtnDropdown>
</template>
<template #menu="{ entity }">
<ClaimDescriptorMenu :claim="entity.claim" />
</template>
<template #body="{ entity: { claim, salesClaimed, developments } }">
<QCard class="vn-one" v-if="$route.name != 'ClaimSummary'">
<VnTitle

View File

@ -93,16 +93,7 @@ defineExpose({ states });
outlined
rounded
dense
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel caption>{{ scope.opt?.name }}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
/>
<VnSelect
:label="t('claim.responsible')"
v-model="params.claimResponsibleFk"

View File

@ -2,18 +2,18 @@
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { toDate } from 'filters/index';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import ClaimFilter from './ClaimFilter.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import ClaimSummary from './Card/ClaimSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import ZoneDescriptorProxy from '../Zone/Card/ZoneDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const dataKey = 'ClaimList';
const claimFilterRef = ref();
const columns = computed(() => [
@ -125,48 +125,50 @@ const STATE_COLOR = {
</script>
<template>
<VnSearchbar
data-key="ClaimList"
:label="t('Search claim')"
:info="t('You can search by claim id or customer name')"
/>
<RightMenu>
<template #right-panel>
<VnSection
:data-key="dataKey"
:columns="columns"
prefix="claim"
:array-data-props="{
url: 'Claims/filter',
order: ['cs.priority ASC', 'created ASC'],
}"
>
<template #rightMenu>
<ClaimFilter data-key="ClaimList" ref="claimFilterRef" />
</template>
</RightMenu>
<VnTable
data-key="ClaimList"
url="Claims/filter"
:order="['cs.priority ASC', 'created ASC']"
:columns="columns"
redirect="claim"
:right-search="false"
>
<template #column-clientFk="{ row }">
<span class="link" @click.stop>
{{ row.clientName }}
<CustomerDescriptorProxy :id="row.clientFk" />
</span>
<template #body>
<VnTable
:data-key="dataKey"
:columns="columns"
redirect="claim"
:right-search="false"
auto-load
>
<template #column-clientFk="{ row }">
<span class="link" @click.stop>
{{ row.clientName }}
<CustomerDescriptorProxy :id="row.clientFk" />
</span>
</template>
<template #column-attendedBy="{ row }">
<span @click.stop>
<VnUserLink :name="row.workerName" :worker-id="row.workerFk" />
</span>
</template>
<template #column-zoneFk="{ row }">
<span class="link" @click.stop>
{{ row.zoneName }}
<ZoneDescriptorProxy :id="row.zoneId" />
</span>
</template>
</VnTable>
</template>
<template #column-attendedBy="{ row }">
<span @click.stop>
<VnUserLink :name="row.workerName" :worker-id="row.workerFk" />
</span>
</template>
<template #column-zoneFk="{ row }">
<span class="link" @click.stop>
{{ row.zoneName }}
<ZoneDescriptorProxy :id="row.zoneId" />
</span>
</template>
</VnTable>
</VnSection>
</template>
<i18n>
es:
Search claim: Buscar reclamación
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
params:
stateCode: Estado
en:

View File

@ -44,3 +44,5 @@ claim:
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}'
noData: 'There are no images/videos, click here or drag and drop the file'
dragDrop: Drag and drop it here
search: Search claims
searchInfo: You can search by claim id or customer name

View File

@ -1,5 +1,3 @@
Search claim: Buscar reclamación
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
claim:
customer: Cliente
code: Código
@ -46,3 +44,5 @@ claim:
fileDescription: 'ID de reclamación {claimId} del cliente {clientName} con ID {clientId}'
noData: 'No hay imágenes/videos, haz clic aquí o arrastra y suelta el archivo'
dragDrop: Arrastra y suelta aquí
search: Buscar reclamación
searchInfo: Puedes buscar por ID de la reclamación o nombre del cliente

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
@ -11,16 +11,9 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
import { useState } from 'src/composables/useState';
const state = useState();
const customer = ref();
onMounted(async () => {
customer.value = state.get('customer');
if (customer.value) customer.value.webAccess = data.value?.account?.isActive;
});
const customerDebt = ref();
const customerCredit = ref();
const $props = defineProps({
id: {
type: Number,
@ -42,10 +35,12 @@ const entityId = computed(() => {
const data = ref(useCardDescription());
const setData = (entity) => {
customerDebt.value = entity?.debt;
customerCredit.value = entity?.credit;
data.value = useCardDescription(entity?.name, entity?.id);
};
const debtWarning = computed(() => {
return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary';
return customerDebt.value > customerCredit.value ? 'negative' : 'primary';
});
</script>
@ -97,26 +92,21 @@ const debtWarning = computed(() => {
:value="entity.businessType.description"
/>
</template>
<template #icons>
<QCardActions v-if="customer" class="q-gutter-x-md">
<template #icons="{ entity }">
<QCardActions class="q-gutter-x-md">
<QIcon
v-if="!customer.isActive"
v-if="!entity.isActive"
name="vn:disabled"
size="xs"
color="primary"
>
<QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip>
</QIcon>
<QIcon
v-if="customer.isFreezed"
name="vn:frozen"
size="xs"
color="primary"
>
<QIcon v-if="entity.isFreezed" name="vn:frozen" size="xs" color="primary">
<QTooltip>{{ t('customer.card.isFrozen') }}</QTooltip>
</QIcon>
<QIcon
v-if="!customer.account?.active"
v-if="!entity.account?.active"
color="primary"
name="vn:noweb"
size="xs"
@ -124,7 +114,7 @@ const debtWarning = computed(() => {
<QTooltip>{{ t('customer.card.webAccountInactive') }}</QTooltip>
</QIcon>
<QIcon
v-if="customer.debt > customer.credit"
v-if="entity.debt > entity.credit"
name="vn:risk"
size="xs"
:color="debtWarning"
@ -132,7 +122,7 @@ const debtWarning = computed(() => {
<QTooltip>{{ t('customer.card.hasDebt') }}</QTooltip>
</QIcon>
<QIcon
v-if="!customer.isTaxDataChecked"
v-if="!entity.isTaxDataChecked"
name="vn:no036"
size="xs"
color="primary"
@ -140,7 +130,7 @@ const debtWarning = computed(() => {
<QTooltip>{{ t('customer.card.notChecked') }}</QTooltip>
</QIcon>
<QBtn
v-if="customer.unpaid"
v-if="entity.unpaid"
flat
size="sm"
icon="vn:Client_unpaid"

View File

@ -63,9 +63,10 @@ const columns = computed(() => [
<template>
<QPage class="column items-center q-pa-md">
<VnTable
data-key="Mandates"
url="Mandates"
:filter="filter"
auto-load
url="Mandates"
:columns="columns"
class="full-width q-mt-md"
:right-search="false"

View File

@ -12,6 +12,7 @@ import VnLinkMail from 'src/components/ui/VnLinkMail.vue';
import CustomerSummaryTable from 'src/pages/Customer/components/CustomerSummaryTable.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
const route = useRoute();
const { t } = useI18n();
const grafanaUrl = 'https://grafana.verdnatura.es';
@ -70,6 +71,9 @@ const sumRisk = ({ clientRisks }) => {
data-key="CustomerSummary"
module-name="Customer"
>
<template #menu="{ entity }">
<CustomerDescriptorMenu :customer="entity" />
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<VnTitle
@ -94,14 +98,13 @@ const sumRisk = ({ clientRisks }) => {
:phone-number="entity.mobile"
:channel="entity.country?.saySimpleCountry?.channel"
class="q-ml-xs"
:country="entity.country?.code"
/>
</template>
</VnLv>
<VnLv :value="entity.email" copy
><template #label>
{{ t('globals.params.email') }}
<VnLinkMail :email="entity.email"></VnLinkMail> </template
<VnLinkMail email="entity.email"></VnLinkMail> </template
></VnLv>
<VnLv
:label="t('customer.summary.salesPerson')"
@ -173,7 +176,7 @@ const sumRisk = ({ clientRisks }) => {
:label="t('customer.summary.notifyByEmail')"
:value="entity.isToBeMailed"
/>
<VnLv :label="t('globals.isVies')" :value="entity.isVies" />
<VnLv :label="t('customer.summary.vies')" :value="entity.isVies" />
</VnRow>
</QCard>
<QCard class="vn-one">

View File

@ -247,8 +247,16 @@ function handleLocation(data, location) {
:label="t('Longitude')"
clearable
v-model="data.longitude"
:decimal-places="7"
:positive="false"
/>
<VnInputNumber
:label="t('Latitude')"
clearable
v-model="data.latitude"
:decimal-places="7"
:positive="false"
/>
<VnInputNumber :label="t('Latitude')" clearable v-model="data.latitude" />
</VnRow>
<h4 class="q-mb-xs">{{ t('Notes') }}</h4>
<VnRow

View File

@ -94,3 +94,24 @@ customer:
hasToInvoiceByAddress: Invoice by address
isToBeMailed: Mailing
hasSepaVnl: VNL B2B received
params:
id: Id
isWorker: Is Worker
payMethod: Payment Method
workerFk: Author
observation: Last Observation
created: Last Update Date
creditInsurance: Credit Insurance
defaulterSinced: Defaulted Since
hasRecovery: Has Recovery
socialName: Social name
city: City
phone: Phone
postcode: Postcode
campaign: Campaign
grouped: Grouped
search: Contains
itemId: Item Id
ticketFk: Ticket Id
description: Description
quantity: Quantity

View File

@ -96,3 +96,24 @@ customer:
hasToInvoiceByAddress: Factura por consigna
isToBeMailed: Env. emails
hasSepaVnl: Recibido B2B VNL
params:
id: ID
isWorker: Es trabajador
payMethod: F. Pago
workerFk: Autor
observation: Última observación
created: Fecha Ú. O.
creditInsurance: Crédito A.
defaulterSinced: Desde
hasRecovery: Tiene recobro
socialName: Razón social
campaign: Campaña
city: Ciudad
phone: Teléfono
postcode: Código postal
grouped: Agrupado
search: Contiene
itemId: Id Artículo
ticketFk: Id Ticket
description: Descripción
quantity: Cantidad

View File

@ -1,13 +1,13 @@
<script setup>
import VnCard from 'components/common/VnCard.vue';
import VnCardBeta from 'components/common/VnCardBeta.vue';
import DepartmentDescriptor from 'pages/Department/Card/DepartmentDescriptor.vue';
</script>
<template>
<VnCard
<VnCardBeta
class="q-pa-md column items-center"
v-bind="{ ...$attrs }"
data-key="Department"
base-url="Departments"
:descriptor="DepartmentDescriptor"
/>
</template>
</template>

View File

@ -83,7 +83,11 @@ const { openConfirmationModal } = useVnConfirm();
</template>
<template #body="{ entity }">
<VnLv :label="t('department.chat')" :value="entity.chatName" />
<VnLv :label="t('globals.email')" :value="entity.notificationEmail" copy />
<VnLv
:label="t('globals.params.email')"
:value="entity.notificationEmail"
copy
/>
<VnLv
:label="t('department.selfConsumptionCustomer')"
:value="entity.client?.name"

View File

@ -40,7 +40,7 @@ onMounted(async () => {
<template #body="{ entity: department }">
<QCard class="column">
<VnTitle
:url="`#/department/department/${entityId}/basic-data`"
:url="`#/worker/department/${entityId}/basic-data`"
:text="t('Basic data')"
/>
<div class="full-width row wrap justify-between content-between">
@ -58,7 +58,7 @@ onMounted(async () => {
dash
/>
<VnLv
:label="t('globals.email')"
:label="t('globals.params.email')"
:value="department.notificationEmail"
dash
/>

View File

@ -7,9 +7,9 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { toDate } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import { getUrl } from 'src/composables/getUrl';
import filter from './EntryFilter.js';
import EntryDescriptorMenu from './EntryDescriptorMenu.vue';
const $props = defineProps({
id: {
@ -21,7 +21,6 @@ const $props = defineProps({
const route = useRoute();
const { t } = useI18n();
const { openReport } = usePrintService();
const entryDescriptorRef = ref(null);
const url = ref();
@ -52,10 +51,6 @@ const getEntryRedirectionFilter = (entry) => {
to,
});
};
const showEntryReport = () => {
openReport(`Entries/${route.params.id}/entry-order-pdf`);
};
</script>
<template>
@ -68,14 +63,12 @@ const showEntryReport = () => {
data-key="Entry"
>
<template #menu="{ entity }">
<QItem v-ripple clickable @click="showEntryReport(entity)">
<QItemSection>{{ t('Show entry report') }}</QItemSection>
</QItem>
<EntryDescriptorMenu :id="entity.id" />
</template>
<template #body="{ entity }">
<VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" />
<VnLv :label="t('globals.shipped')" :value="toDate(entity.travel?.shipped)" />
<VnLv :label="t('globals.landed')" :value="toDate(entity.travel?.landed)" />
<VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" />
<VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" />
<VnLv
:label="t('globals.warehouseOut')"
:value="entity.travel?.warehouseOut?.name"
@ -154,7 +147,6 @@ es:
Supplier card: Ficha del proveedor
All travels with current agency: Todos los envíos con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido
Go to module index: Ir al índice del modulo
Inventory entry: Es inventario
Virtual entry: Es una redada

View File

@ -0,0 +1,22 @@
<script setup>
import { usePrintService } from 'composables/usePrintService';
const { openReport } = usePrintService();
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
function showEntryReport() {
openReport(`Entries/${$props.id}/entry-order-pdf`);
}
</script>
<template>
<QItem v-ripple clickable @click="showEntryReport">
<QItemSection>{{ $t('entryList.list.showEntryReport') }}</QItemSection>
</QItem>
</template>

View File

@ -12,6 +12,9 @@ import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnToSummary from 'src/components/ui/VnToSummary.vue';
import EntryDescriptorMenu from './EntryDescriptorMenu.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
const route = useRoute();
const { t } = useI18n();
@ -147,9 +150,8 @@ async function setEntryData(data) {
}
const fetchEntryBuys = async () => {
const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`);
if (data) entryBuys.value = data;
const { data } = await axios.get(`Entries/${entry.value.id}/getBuys`);
if (data) entryBuys.value = data;
};
</script>
@ -171,15 +173,15 @@ const fetchEntryBuys = async () => {
<template #header>
<span>{{ entry.id }} - {{ entry.supplier.nickname }}</span>
</template>
<template #menu="{ entity }">
<EntryDescriptorMenu :id="entity.id" />
</template>
<template #body>
<QCard class="vn-one">
<router-link
:to="{ name: 'EntryBasicData', params: { id: entityId } }"
class="header header-link"
>
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" />
</router-link>
<VnTitle
:url="`#/entry/${entityId}/basic-data`"
:text="t('globals.summary.basicData')"
/>
<VnLv :label="t('entry.summary.commission')" :value="entry.commission" />
<VnLv
:label="t('entry.summary.currency')"
@ -193,13 +195,10 @@ const fetchEntryBuys = async () => {
/>
</QCard>
<QCard class="vn-one">
<router-link
:to="{ name: 'EntryBasicData', params: { id: entityId } }"
class="header header-link"
>
{{ t('globals.summary.basicData') }}
<QIcon name="open_in_new" />
</router-link>
<VnTitle
:url="`#/entry/${entityId}/basic-data`"
:text="t('globals.summary.basicData')"
/>
<VnLv :label="t('entry.summary.travelReference')">
<template #value>
<span class="link">
@ -212,61 +211,45 @@ const fetchEntryBuys = async () => {
:label="t('entry.summary.travelAgency')"
:value="entry.travel.agency?.name"
/>
<VnLv :label="t('globals.shipped')" :value="toDate(entry.travel.shipped)" />
<VnLv
:label="t('globals.shipped')"
:value="toDate(entry.travel.shipped)"
/>
<VnLv
:label="t('globals.warehouseOut')"
:value="entry.travel.warehouseOut?.name"
/>
<QCheckbox
<VnLv
:label="t('entry.summary.travelDelivered')"
v-model="entry.travel.isDelivered"
:disable="true"
:value="entry.travel.isDelivered"
/>
<VnLv :label="t('globals.landed')" :value="toDate(entry.travel.landed)" />
<VnLv
:label="t('globals.warehouseIn')"
:value="entry.travel.warehouseIn?.name"
/>
<QCheckbox
<VnLv
:label="t('entry.summary.travelReceived')"
v-model="entry.travel.isReceived"
:disable="true"
:value="entry.travel.isReceived"
/>
</QCard>
<QCard class="vn-one">
<router-link
:to="{ name: 'TravelSummary', params: { id: entry.travel.id } }"
class="header header-link"
>
{{ t('Travel data') }}
<QIcon name="open_in_new" />
</router-link>
<QCheckbox
:label="t('entry.summary.ordered')"
v-model="entry.isOrdered"
:disable="true"
/>
<QCheckbox
:label="t('globals.confirmed')"
v-model="entry.isConfirmed"
:disable="true"
/>
<QCheckbox
:label="t('entry.summary.booked')"
v-model="entry.isBooked"
:disable="true"
/>
<QCheckbox
:label="t('entry.summary.excludedFromAvailable')"
v-model="entry.isExcludedFromAvailable"
:disable="true"
/>
<VnTitle :url="`#/travel/${entityId}/summary`" :text="t('Travel data')" />
<VnRow class="block">
<VnLv :label="t('entry.summary.ordered')" :value="entry.isOrdered" />
<VnLv :label="t('globals.confirmed')" :value="entry.isConfirmed" />
<VnLv :label="t('entry.summary.booked')" :value="entry.isBooked" />
<VnLv
:label="t('entry.summary.excludedFromAvailable')"
:value="entry.isExcludedFromAvailable"
/>
</VnRow>
</QCard>
<QCard class="vn-two" style="min-width: 100%">
<a class="header header-link">
{{ t('entry.summary.buys') }}
<QIcon name="open_in_new" />
</a>
<QCard class="vn-max">
<VnTitle
:url="`#/entry/${entityId}/buys`"
:text="t('entry.summary.buys')"
/>
<QTable
:rows="entryBuys"
:columns="entriesTableColumns"

View File

@ -1,24 +1,24 @@
<script setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { usePrintService } from 'composables/usePrintService';
const { openReport } = usePrintService();
const { openReport } = usePrintService();
const buyRows = ref([]);
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: String,
type: Number,
required: false,
default: null,
},
});
const entityId = computed(() => $props.id || route.params.id);
const entriesTableColumns = computed(() => [
{
align: 'left',
@ -63,34 +63,39 @@ const entriesTableColumns = computed(() => [
field: 'grouping',
},
]);
</script>
function downloadCSV(rows) {
const headers = ['id', 'itemFk', 'name', 'stickers', 'packing', 'comment'];
const csvRows = rows.map((row) => {
const buy = row;
const item = buy.item || {};
return [
buy.id,
buy.itemFk,
item.name || '',
buy.stickers,
buy.packing,
item.comment || '',
].join(',');
});
const csvContent = [headers.join(','), ...csvRows].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${entityId.value}data.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
</script>
<template>
<QDialog ref="dialogRef">
<QCard style="min-width: 800px">
<QCardSection class="row items-center q-pb-none">
<QAvatar
:icon="icon"
color="primary"
text-color="white"
size="xl"
v-if="icon"
/>
<span class="text-h6 text-grey">{{ title }}</span>
<QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
</QCardSection>
<QCardActions align="right">
<QBtn
:label="t('myEntries.printLabels')"
color="primary"
icon="print"
:loading="isLoading"
@click="openReport(`Entries/${entityId}/labelSupplier`)"
unelevated
autofocus
/>
</QCardActions>
<QCardSection class="row items-center">
<VnPaginate
ref="entryBuysPaginateRef"
@ -101,6 +106,7 @@ const entriesTableColumns = computed(() => [
>
<template #body="{ rows }">
<QTable
ref="buyRows"
:rows="rows"
:columns="entriesTableColumns"
row-key="id"
@ -110,6 +116,26 @@ const entriesTableColumns = computed(() => [
:grid="$q.screen.lt.md"
:no-data-label="t('globals.noResults')"
>
<template #top-left>
<QBtn
:label="t('myEntries.downloadCsv')"
color="primary"
icon="csv"
@click="downloadCSV(rows)"
unelevated
/>
</template>
<template #top-right>
<QBtn
class="q-mr-lg"
:label="t('myEntries.printLabels')"
color="primary"
icon="print"
@click="
openReport(`Entries/${entityId}/labelSupplier`)
"
/>
</template>
<template #body="props">
<QTr>
<QTd v-for="col in props.cols" :key="col.name">
@ -118,7 +144,6 @@ const entriesTableColumns = computed(() => [
<QBtn
icon="visibility"
v-if="props.row.stickers > 0"
:loading="isLoading"
@click="
openReport(
`Entries/${props.row.id}/buy-label-supplier`

View File

@ -123,6 +123,7 @@ const companiesOptions = ref([]);
option-value="id"
option-label="name"
:fields="['id', 'name', 'nickname']"
:filter-options="['id', 'name', 'nickname']"
sort-by="nickname"
hide-selected
dense
@ -132,9 +133,12 @@ const companiesOptions = ref([]);
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
scope.opt?.name + ': ' + scope.opt?.nickname
}}</QItemLabel>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>

View File

@ -69,12 +69,14 @@ const tagValues = ref([]);
use-input
@update:model-value="searchFn()"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }}
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>

View File

@ -1,9 +1,8 @@
<script setup>
import { onMounted, ref, computed } from 'vue';
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import EntryFilter from './EntryFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import VnTable from 'components/VnTable/VnTable.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import { toDate } from 'src/filters';
@ -12,7 +11,6 @@ import EntrySummary from './Card/EntrySummary.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const tableRef = ref();
@ -245,7 +243,6 @@ const columns = computed(() => [
<i18n>
es:
Inventory entry: Es inventario
Virtual entry: Es una redada
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada

View File

@ -102,7 +102,7 @@ const columns = computed(() => [
actions: [
{
title: t('myEntries.printLabels'),
icon: 'print',
icon: 'move_item',
isPrimary: true,
action: (row) => printBuys(row.id),
},

View File

@ -1,6 +1,7 @@
entryList:
list:
inventoryEntry: Inventory entry
showEntryReport: Show entry report
entryFilter:
filter:
search: General search
@ -17,5 +18,6 @@ myEntries:
warehouseInFk: Warehouse in
daysOnward: Days onward
daysAgo: Days ago
downloadCsv: Download CSV
wasteRecalc:
recalcOk: The wastes were successfully recalculated

View File

@ -4,6 +4,7 @@ You can search by entry reference: Puedes buscar por referencia de la entrada
entryList:
list:
inventoryEntry: Es inventario
showEntryReport: Ver informe del pedido
entryFilter:
filter:
search: Búsqueda general
@ -20,5 +21,6 @@ myEntries:
warehouseInFk: Alm. entrada
daysOnward: Días adelante
daysAgo: Días atras
downloadCsv: Descargar CSV
wasteRecalc:
recalcOk: Se han recalculado las mermas correctamente

View File

@ -268,7 +268,7 @@ function deleteFile(dmsFk) {
</VnRow>
<VnRow>
<VnSelect
:label="t('InvoiceIn.summary.sage')"
:label="t('invoicein.summary.sage')"
v-model="data.withholdingSageFk"
:options="sageWithholdings"
option-value="id"

View File

@ -1,60 +1,30 @@
<script setup>
import { ref, reactive, computed, onBeforeMount, capitalize } from 'vue';
import { ref, reactive, computed, onBeforeMount } from 'vue';
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { toCurrency, toDate } from 'src/filters';
import { useAcl } from 'src/composables/useAcl';
import { downloadFile } from 'src/composables/downloadFile';
import { useArrayData } from 'src/composables/useArrayData';
import { usePrintService } from 'composables/usePrintService';
import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import FetchData from 'src/components/FetchData.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useCapitalize } from 'src/composables/useCapitalize';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceInToBook from '../InvoiceInToBook.vue';
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
const $props = defineProps({ id: { type: Number, default: null } });
const { push, currentRoute } = useRouter();
const quasar = useQuasar();
const { hasAny } = useAcl();
const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data);
const cardDescriptorRef = ref();
const correctionDialogRef = ref();
const entityId = computed(() => $props.id || +currentRoute.value.params.id);
const totalAmount = ref();
const currentAction = ref();
const config = ref();
const cplusRectificationTypes = ref([]);
const siiTypeInvoiceIns = ref([]);
const invoiceCorrectionTypes = ref([]);
const actions = {
unbook: {
title: t('assertAction', { action: t('unbook') }),
action: toUnbook,
},
delete: {
title: t('assertAction', { action: t('delete') }),
action: deleteInvoice,
},
clone: {
title: t('assertAction', { action: t('clone') }),
action: cloneInvoice,
},
showPdf: { cb: showPdfInvoice },
sendPdf: { cb: sendPdfInvoiceConfirmation },
correct: { cb: () => correctionDialogRef.value.show() },
};
const filter = {
include: [
{
@ -90,7 +60,7 @@ const routes = reactive({
return {
name: 'InvoiceInList',
query: {
table: JSON.stringify({ supplierFk: id }),
params: JSON.stringify({ supplierFk: id }),
},
};
},
@ -99,7 +69,7 @@ const routes = reactive({
return {
name: 'InvoiceInList',
query: {
table: JSON.stringify({ correctedFk: entityId.value }),
params: JSON.stringify({ correctedFk: entityId.value }),
},
};
}
@ -118,21 +88,21 @@ const routes = reactive({
const correctionFormData = reactive({
invoiceReason: 2,
invoiceType: 2,
invoiceClass: 8,
invoiceClass: 6,
});
const isNotFilled = computed(() => Object.values(correctionFormData).includes(null));
onBeforeMount(async () => {
await setInvoiceCorrection(entityId.value);
const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`);
totalAmount.value = data.totalTaxableBase;
totalAmount.value = data.totalDueDay;
});
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
await setInvoiceCorrection(to.params.id);
const { data } = await axios.get(`InvoiceIns/${to.params.id}/getTotals`);
totalAmount.value = data.totalTaxableBase;
totalAmount.value = data.totalDueDay;
}
});
@ -153,94 +123,6 @@ async function setInvoiceCorrection(id) {
);
}
function openDialog() {
quasar.dialog({
component: VnConfirm,
componentProps: {
title: t(currentAction.value.title),
promise: currentAction.value.action,
},
});
}
async function toUnbook() {
const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`);
const { isLinked, bookEntry, accountingEntries } = data;
const type = isLinked ? 'warning' : 'positive';
const message = isLinked
? t('isLinked', { bookEntry, accountingEntries })
: t('isNotLinked', { bookEntry });
quasar.notify({ type, message });
if (!isLinked) arrayData.store.data.isBooked = false;
}
async function deleteInvoice() {
await axios.delete(`InvoiceIns/${entityId.value}`);
quasar.notify({
type: 'positive',
message: t('Invoice deleted'),
});
push({ path: '/invoice-in' });
}
async function cloneInvoice() {
const { data } = await axios.post(`InvoiceIns/${entityId.value}/clone`);
quasar.notify({
type: 'positive',
message: t('Invoice cloned'),
});
push({ path: `/invoice-in/${data.id}/summary` });
}
const canEditProp = (props) =>
hasAny([{ model: 'InvoiceIn', props, accessType: 'WRITE' }]);
const isAgricultural = () => {
if (!config.value) return false;
return (
invoiceIn.value?.supplier?.sageFarmerWithholdingFk ===
config?.value[0]?.sageWithholdingFk
);
};
function showPdfInvoice() {
if (isAgricultural())
openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`, null, '_blank');
}
function sendPdfInvoiceConfirmation() {
quasar.dialog({
component: SendEmailDialog,
componentProps: {
data: {
address: invoiceIn.value.supplier.contacts[0].email,
},
promise: sendPdfInvoice,
},
});
}
function sendPdfInvoice({ address }) {
if (!address)
quasar.notify({
type: 'negative',
message: t(`The email can't be empty`),
});
else
return sendEmail(`InvoiceIns/${entityId.value}/invoice-in-email`, {
recipientId: invoiceIn.value.supplier.id,
recipient: address,
});
}
function triggerMenu(type) {
currentAction.value = actions[type];
if (currentAction.value.cb) currentAction.value.cb();
else openDialog(type);
}
const createInvoiceInCorrection = async () => {
const { data: correctingId } = await axios.post(
'InvoiceIns/corrective',
@ -262,7 +144,7 @@ const createInvoiceInCorrection = async () => {
auto-load
/>
<FetchData
url="siiTypeInvoiceIns"
url="SiiTypeInvoiceIns"
:where="{ code: { like: 'R%' } }"
@on-fetch="(data) => (siiTypeInvoiceIns = data)"
auto-load
@ -281,87 +163,13 @@ const createInvoiceInCorrection = async () => {
title="supplierRef"
>
<template #menu="{ entity }">
<InvoiceInToBook>
<template #content="{ book }">
<QItem
v-if="!entity?.isBooked && canEditProp('toBook')"
v-ripple
clickable
@click="book(entityId)"
>
<QItemSection>{{ t('To book') }}</QItemSection>
</QItem>
</template>
</InvoiceInToBook>
<QItem
v-if="entity?.isBooked && canEditProp('toUnbook')"
v-ripple
clickable
@click="triggerMenu('unbook')"
>
<QItemSection>
{{ t('To unbook') }}
</QItemSection>
</QItem>
<QItem
v-if="canEditProp('deleteById')"
v-ripple
clickable
@click="triggerMenu('delete')"
>
<QItemSection>{{ t('Delete invoice') }}</QItemSection>
</QItem>
<QItem
v-if="canEditProp('clone')"
v-ripple
clickable
@click="triggerMenu('clone')"
>
<QItemSection>{{ t('Clone invoice') }}</QItemSection>
</QItem>
<QItem
v-if="isAgricultural()"
v-ripple
clickable
@click="triggerMenu('showPdf')"
>
<QItemSection>{{ t('Show agricultural receipt as PDF') }}</QItemSection>
</QItem>
<QItem
v-if="isAgricultural()"
v-ripple
clickable
@click="triggerMenu('sendPdf')"
>
<QItemSection
>{{ t('Send agricultural receipt as PDF') }}...</QItemSection
>
</QItem>
<QItem
v-if="!invoiceInCorrection.corrected"
v-ripple
clickable
@click="triggerMenu('correct')"
>
<QItemSection>{{ t('Create rectificative invoice') }}...</QItemSection>
</QItem>
<QItem
v-if="entity.dmsFk"
v-ripple
clickable
@click="downloadFile(entity.dmsFk)"
>
<QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection>
</QItem>
<InvoiceInDescriptorMenu :invoice="entity" />
</template>
<template #body="{ entity }">
<VnLv :label="t('InvoiceIn.list.issued')" :value="toDate(entity.issued)" />
<VnLv
:label="t('InvoiceIn.summary.bookedDate')"
:value="toDate(entity.booked)"
/>
<VnLv :label="t('InvoiceIn.list.amount')" :value="toCurrency(totalAmount)" />
<VnLv :label="t('InvoiceIn.list.supplier')">
<VnLv :label="t('invoicein.list.issued')" :value="toDate(entity.issued)" />
<VnLv :label="t('invoicein.summary.bookedDate')" :value="toDate(entity.booked)" />
<VnLv :label="t('invoicein.list.amount')" :value="toCurrency(totalAmount)" />
<VnLv :label="t('invoicein.list.supplier')">
<template #value>
<span class="link">
{{ entity?.supplier?.nickname }}
@ -378,7 +186,7 @@ const createInvoiceInCorrection = async () => {
color="primary"
:to="routes.getSupplier(entity.supplierFk)"
>
<QTooltip>{{ t('InvoiceIn.list.supplier') }}</QTooltip>
<QTooltip>{{ t('invoicein.list.supplier') }}</QTooltip>
</QBtn>
<QBtn
size="md"
@ -394,7 +202,7 @@ const createInvoiceInCorrection = async () => {
color="primary"
:to="routes.getTickets(entity.supplierFk)"
>
<QTooltip>{{ t('InvoiceIn.descriptor.ticketList') }}</QTooltip>
<QTooltip>{{ t('InvoiceOut.card.ticketList') }}</QTooltip>
</QBtn>
<QBtn
v-if="
@ -438,7 +246,7 @@ const createInvoiceInCorrection = async () => {
readonly
/>
<VnSelect
:label="`${capitalize(t('globals.class'))}`"
:label="`${useCapitalize(t('globals.class'))}`"
v-model="correctionFormData.invoiceClass"
:options="siiTypeInvoiceIns"
option-value="id"
@ -448,27 +256,15 @@ const createInvoiceInCorrection = async () => {
</QItemSection>
<QItemSection>
<VnSelect
:label="`${capitalize(t('globals.type'))}`"
:label="`${useCapitalize(t('globals.type'))}`"
v-model="correctionFormData.invoiceType"
:options="cplusRectificationTypes"
option-value="id"
option-label="description"
:required="true"
>
<template #option="{ opt }">
<QItem>
<QItemSection>
<QItemLabel
>{{ opt.code }} -
{{ opt.description }}</QItemLabel
>
</QItemSection>
</QItem>
<div></div>
</template>
</VnSelect>
/>
<VnSelect
:label="`${capitalize(t('globals.reason'))}`"
:label="`${useCapitalize(t('globals.reason'))}`"
v-model="correctionFormData.invoiceReason"
:options="invoiceCorrectionTypes"
option-value="id"
@ -512,31 +308,3 @@ const createInvoiceInCorrection = async () => {
}
}
</style>
<i18n>
en:
isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries
isLinked: The entry {bookEntry} has been linked to Sage. Please contact administration for further information
assertAction: Are you sure you want to {action} this invoice?
es:
book: asentar
unbook: desasentar
delete: eliminar
clone: clonar
To book: Contabilizar
To unbook: Descontabilizar
Delete invoice: Eliminar factura
Invoice deleted: Factura eliminada
Clone invoice: Clonar factura
Invoice cloned: Factura clonada
Show agricultural receipt as PDF: Ver recibo agrícola como PDF
Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
Are you sure you want to send it?: Estás seguro que quieres enviarlo?
Send PDF invoice: Enviar factura a PDF
Create rectificative invoice: Crear factura rectificativa
Rectificative invoice: Factura rectificativa
Original invoice: Factura origen
Entry: entrada
isNotLinked: Se ha eliminado el asiento {bookEntry} con {accountingEntries} apuntes
isLinked: El asiento {bookEntry} fue enlazado a Sage, por favor contacta con administración
assertAction: Estas seguro de querer {action} esta factura?
</i18n>

View File

@ -0,0 +1,206 @@
<script setup>
import { ref, computed, toRefs, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { useAcl } from 'src/composables/useAcl';
import { downloadFile } from 'src/composables/downloadFile';
import { useArrayData } from 'src/composables/useArrayData';
import { usePrintService } from 'composables/usePrintService';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import SendEmailDialog from 'components/common/SendEmailDialog.vue';
import InvoiceInToBook from '../InvoiceInToBook.vue';
const { hasAny } = useAcl();
const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
const { push, currentRoute } = useRouter();
const $props = defineProps({
invoice: {
type: Object,
required: true,
},
});
const { invoice } = toRefs($props);
const quasar = useQuasar();
const arrayData = useArrayData();
const currentAction = ref();
const config = ref();
const correctionDialogRef = ref();
const invoiceInCorrection = reactive({ correcting: [], corrected: null });
const entityId = computed(() => $props.invoice.id || +currentRoute.value.params.id);
const invoiceIn = computed(() => arrayData.store.data);
const actions = {
unbook: {
title: t('assertAction', { action: t('invoicein.descriptorMenu.unbook') }),
action: toUnbook,
},
delete: {
title: t('assertAction', { action: t('invoicein.descriptorMenu.delete') }),
action: deleteInvoice,
},
clone: {
title: t('assertAction', { action: t('invoicein.descriptorMenu.clone') }),
action: cloneInvoice,
},
showPdf: { cb: showPdfInvoice },
sendPdf: { cb: sendPdfInvoiceConfirmation },
correct: { cb: () => correctionDialogRef.value.show() },
};
const canEditProp = (props) =>
hasAny([{ model: 'InvoiceIn', props, accessType: 'WRITE' }]);
function triggerMenu(type) {
currentAction.value = actions[type];
if (currentAction.value.cb) currentAction.value.cb();
else openDialog(type);
}
function openDialog() {
quasar.dialog({
component: VnConfirm,
componentProps: {
title: t(currentAction.value.title),
promise: currentAction.value.action,
},
});
}
async function toUnbook() {
const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`);
const { isLinked, bookEntry, accountingEntries } = data;
const type = isLinked ? 'warning' : 'positive';
const message = isLinked
? t('isLinked', { bookEntry })
: t('isNotLinked', { bookEntry, accountingEntries });
quasar.notify({ type, message });
if (!isLinked) arrayData.store.data.isBooked = false;
}
async function deleteInvoice() {
await axios.delete(`InvoiceIns/${entityId.value}`);
quasar.notify({
type: 'positive',
message: t('invoicein.descriptorMenu.invoiceDeleted'),
});
push({ path: '/invoice-in' });
}
async function cloneInvoice() {
const { data } = await axios.post(`InvoiceIns/${entityId.value}/clone`);
quasar.notify({
type: 'positive',
message: t('invoicein.descriptorMenu.invoiceCloned'),
});
push({ path: `/invoice-in/${data.id}/summary` });
}
const isAgricultural = () => {
if (!config.value) return false;
return (
invoiceIn.value?.supplier?.sageFarmerWithholdingFk ===
config?.value[0]?.sageWithholdingFk
);
};
function showPdfInvoice() {
if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`);
}
function sendPdfInvoiceConfirmation() {
quasar.dialog({
component: SendEmailDialog,
componentProps: {
data: {
address: invoiceIn.value.supplier.contacts[0].email,
},
promise: sendPdfInvoice,
},
});
}
function sendPdfInvoice({ address }) {
if (!address)
quasar.notify({
type: 'negative',
message: t(`The email can't be empty`),
});
else
return sendEmail(`InvoiceIns/${entityId.value}/invoice-in-email`, {
recipientId: invoiceIn.value.supplier.id,
recipient: address,
});
}
</script>
<template>
<InvoiceInToBook>
<template #content="{ book }">
<QItem
v-if="!invoice?.isBooked && canEditProp('toBook')"
v-ripple
clickable
@click="book(entityId)"
>
<QItemSection>{{ t('invoicein.descriptorMenu.toBook') }}</QItemSection>
</QItem>
</template>
</InvoiceInToBook>
<QItem
v-if="invoice?.isBooked && canEditProp('toUnbook')"
v-ripple
clickable
@click="triggerMenu('unbook')"
>
<QItemSection>
{{ t('invoicein.descriptorMenu.toUnbook') }}
</QItemSection>
</QItem>
<QItem
v-if="canEditProp('deleteById')"
v-ripple
clickable
@click="triggerMenu('invoicein.descriptorMenu.delete')"
>
<QItemSection>{{ t('invoicein.descriptorMenu.deleteInvoice') }}</QItemSection>
</QItem>
<QItem v-if="canEditProp('clone')" v-ripple clickable @click="triggerMenu('clone')">
<QItemSection>{{ t('invoicein.descriptorMenu.cloneInvoice') }}</QItemSection>
</QItem>
<QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')">
<QItemSection>{{
t('invoicein.descriptorMenu.showAgriculturalPdf')
}}</QItemSection>
</QItem>
<QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')">
<QItemSection
>{{ t('invoicein.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection
>
</QItem>
<QItem
v-if="!invoiceInCorrection.corrected"
v-ripple
clickable
@click="triggerMenu('correct')"
>
<QItemSection
>{{ t('invoicein.descriptorMenu.createCorrective') }}...</QItemSection
>
</QItem>
<QItem v-if="invoice.dmsFk" v-ripple clickable @click="downloadFile(invoice.dmsFk)">
<QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection>
</QItem>
</template>
<i18n>
en:
isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries
isLinked: The entry has been linked to Sage. Please contact administration for further information
assertAction: Are you sure you want to {action} this invoice?
es:
isNotLinked: Se ha eliminado el asiento {bookEntry} con {accountingEntries} apuntes
isLinked: El asiento fue enlazado a Sage, por favor contacta con administración
assertAction: Estas seguro de querer {action} esta factura?
</i18n>

View File

@ -10,6 +10,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import InvoiceIntoBook from '../InvoiceInToBook.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
const props = defineProps({ id: { type: [Number, String], default: 0 } });
const { t } = useI18n();
@ -26,14 +27,14 @@ const intrastatTotals = ref({ amount: 0, net: 0, stems: 0 });
const vatColumns = ref([
{
name: 'expense',
label: 'InvoiceIn.summary.expense',
label: 'invoicein.summary.expense',
field: (row) => row.expenseFk,
sortable: true,
align: 'left',
},
{
name: 'landed',
label: 'InvoiceIn.summary.taxableBase',
label: 'invoicein.summary.taxableBase',
field: (row) => row.taxableBase,
format: (value) => toCurrency(value),
sortable: true,
@ -41,7 +42,7 @@ const vatColumns = ref([
},
{
name: 'vat',
label: 'InvoiceIn.summary.sageVat',
label: 'invoicein.summary.sageVat',
field: (row) => {
if (row.taxTypeSage) return `#${row.taxTypeSage.id} : ${row.taxTypeSage.vat}`;
},
@ -51,7 +52,7 @@ const vatColumns = ref([
},
{
name: 'transaction',
label: 'InvoiceIn.summary.sageTransaction',
label: 'invoicein.summary.sageTransaction',
field: (row) => {
if (row.transactionTypeSage)
return `#${row.transactionTypeSage.id} : ${row.transactionTypeSage?.transaction}`;
@ -62,7 +63,7 @@ const vatColumns = ref([
},
{
name: 'rate',
label: 'InvoiceIn.summary.rate',
label: 'invoicein.summary.rate',
field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate),
format: (value) => toCurrency(value),
sortable: true,
@ -70,7 +71,7 @@ const vatColumns = ref([
},
{
name: 'currency',
label: 'InvoiceIn.summary.currency',
label: 'invoicein.summary.currency',
field: (row) => row.foreignValue,
format: (val) => val && toCurrency(val, currency.value),
sortable: true,
@ -81,21 +82,21 @@ const vatColumns = ref([
const dueDayColumns = ref([
{
name: 'date',
label: 'InvoiceIn.summary.dueDay',
label: 'invoicein.summary.dueDay',
field: (row) => toDate(row.dueDated),
sortable: true,
align: 'left',
},
{
name: 'bank',
label: 'InvoiceIn.summary.bank',
label: 'invoicein.summary.bank',
field: (row) => row.bank.bank,
sortable: true,
align: 'left',
},
{
name: 'amount',
label: 'InvoiceIn.list.amount',
label: 'invoicein.list.amount',
field: (row) => row.amount,
format: (value) => toCurrency(value),
sortable: true,
@ -103,7 +104,7 @@ const dueDayColumns = ref([
},
{
name: 'landed',
label: 'InvoiceIn.summary.foreignValue',
label: 'invoicein.summary.foreignValue',
field: (row) => row.foreignValue,
format: (val) => val && toCurrency(val, currency.value),
sortable: true,
@ -114,7 +115,7 @@ const dueDayColumns = ref([
const intrastatColumns = ref([
{
name: 'code',
label: 'InvoiceIn.summary.code',
label: 'invoicein.summary.code',
field: (row) => {
return `${row.intrastat.id}: ${row.intrastat?.description}`;
},
@ -123,21 +124,21 @@ const intrastatColumns = ref([
},
{
name: 'amount',
label: 'InvoiceIn.list.amount',
label: 'invoicein.list.amount',
field: (row) => toCurrency(row.amount),
sortable: true,
align: 'left',
},
{
name: 'net',
label: 'InvoiceIn.summary.net',
label: 'invoicein.summary.net',
field: (row) => row.net,
sortable: true,
align: 'left',
},
{
name: 'stems',
label: 'InvoiceIn.summary.stems',
label: 'invoicein.summary.stems',
field: (row) => row.stems,
format: (value) => value,
sortable: true,
@ -145,7 +146,7 @@ const intrastatColumns = ref([
},
{
name: 'landed',
label: 'InvoiceIn.summary.country',
label: 'invoicein.summary.country',
field: (row) => row.country?.code,
format: (value) => value,
sortable: true,
@ -200,6 +201,9 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</template>
</InvoiceIntoBook>
</template>
<template #menu="{ entity }">
<InvoiceInDescriptorMenu :invoice="entity" />
</template>
<template #body="{ entity }">
<!--Basic Data-->
<QCard class="vn-one">
@ -210,7 +214,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
/>
</QCardSection>
<VnLv
:label="t('InvoiceIn.list.supplier')"
:label="t('invoicein.list.supplier')"
:value="entity.supplier?.name"
>
<template #value>
@ -221,14 +225,14 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</template>
</VnLv>
<VnLv
:label="t('InvoiceIn.list.supplierRef')"
:label="t('invoicein.list.supplierRef')"
:value="entity.supplierRef"
/>
<VnLv
:label="t('InvoiceIn.summary.currency')"
:label="t('invoicein.summary.currency')"
:value="entity.currency?.code"
/>
<VnLv :label="t('InvoiceIn.serial')" :value="`${entity.serial}`" />
<VnLv :label="t('invoicein.serial')" :value="`${entity.serial}`" />
<VnLv
:label="t('globals.country')"
:value="entity.supplier?.country?.code"
@ -243,19 +247,19 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCardSection>
<VnLv
:ellipsis-value="false"
:label="t('InvoiceIn.summary.issued')"
:label="t('invoicein.summary.issued')"
:value="toDate(entity.issued)"
/>
<VnLv
:label="t('InvoiceIn.summary.operated')"
:label="t('invoicein.summary.operated')"
:value="toDate(entity.operated)"
/>
<VnLv
:label="t('InvoiceIn.summary.bookEntried')"
:label="t('invoicein.summary.bookEntried')"
:value="toDate(entity.bookEntried)"
/>
<VnLv
:label="t('InvoiceIn.summary.bookedDate')"
:label="t('invoicein.summary.bookedDate')"
:value="toDate(entity.booked)"
/>
<VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" />
@ -268,18 +272,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
/>
</QCardSection>
<VnLv
:label="t('InvoiceIn.summary.sage')"
:label="t('invoicein.summary.sage')"
:value="entity.sageWithholding?.withholding"
/>
<VnLv
:label="t('InvoiceIn.summary.vat')"
:label="t('invoicein.summary.vat')"
:value="entity.expenseDeductible?.name"
/>
<VnLv
:label="t('InvoiceIn.card.company')"
:label="t('invoicein.card.company')"
:value="entity.company?.code"
/>
<VnLv :label="t('InvoiceIn.isBooked')" :value="invoiceIn?.isBooked" />
<VnLv :label="t('invoicein.isBooked')" :value="invoiceIn?.isBooked" />
</QCard>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
@ -290,11 +294,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCardSection>
<QCardSection class="q-pa-none">
<VnLv
:label="t('InvoiceIn.summary.taxableBase')"
:label="t('invoicein.summary.taxableBase')"
:value="toCurrency(entity.totals.totalTaxableBase)"
/>
<VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" />
<VnLv :label="t('InvoiceIn.summary.dueTotal')">
<VnLv :label="t('invoicein.summary.dueTotal')">
<template #value>
<QChip
dense
@ -302,8 +306,8 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
:color="amountsNotMatch ? 'negative' : 'transparent'"
:title="
amountsNotMatch
? t('InvoiceIn.summary.noMatch')
: t('InvoiceIn.summary.dueTotal')
? t('invoicein.summary.noMatch')
: t('invoicein.summary.dueTotal')
"
>
{{ toCurrency(entity.totals.totalDueDay) }}
@ -314,7 +318,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCard>
<!--Vat-->
<QCard v-if="entity.invoiceInTax.length" class="vat">
<VnTitle :url="getLink('vat')" :text="t('InvoiceIn.card.vat')" />
<VnTitle :url="getLink('vat')" :text="t('invoicein.card.vat')" />
<QTable
:columns="vatColumns"
:rows="entity.invoiceInTax"
@ -362,7 +366,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QCard>
<!--Due Day-->
<QCard v-if="entity.invoiceInDueDay.length" class="due-day">
<VnTitle :url="getLink('due-day')" :text="t('InvoiceIn.card.dueDay')" />
<VnTitle :url="getLink('due-day')" :text="t('invoicein.card.dueDay')" />
<QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat>
<template #header="dueDayProps">
<QTr :props="dueDayProps" class="bg">
@ -400,7 +404,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCard v-if="entity.invoiceInIntrastat.length">
<VnTitle
:url="getLink('intrastat')"
:text="t('InvoiceIn.card.intrastat')"
:text="t('invoicein.card.intrastat')"
/>
<QTable
:columns="intrastatColumns"

View File

@ -83,7 +83,7 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
</template>
</VnSelect>
<VnInput
:label="t('InvoiceIn.list.supplierRef')"
:label="t('invoicein.list.supplierRef')"
v-model="data.supplierRef"
/>
</VnRow>
@ -97,10 +97,10 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
map-options
hide-selected
:required="true"
:rules="validate('InvoiceIn.companyFk')"
:rules="validate('invoicein.companyFk')"
/>
<VnInputDate
:label="t('InvoiceIn.summary.issued')"
:label="t('invoicein.summary.issued')"
v-model="data.issued"
/>
</VnRow>

View File

@ -68,13 +68,26 @@ function handleDaysAgo(params, daysAgo) {
<VnSelect
v-model="params.supplierFk"
url="Suppliers"
:fields="['id', 'nickname']"
:fields="['id', 'nickname', 'name']"
:label="getLocale('supplierFk')"
option-label="nickname"
dense
outlined
rounded
/>
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name}}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id } , ${ scope.opt?.nickname}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
@ -151,7 +164,7 @@ function handleDaysAgo(params, daysAgo) {
<QItem>
<QItemSection>
<QCheckbox
:label="$t('InvoiceIn.isBooked')"
:label="$t('invoicein.isBooked')"
v-model="params.isBooked"
@update:model-value="searchFn()"
toggle-indeterminate

View File

@ -26,7 +26,7 @@ const cols = computed(() => [
{
align: 'left',
name: 'isBooked',
label: t('InvoiceIn.isBooked'),
label: t('invoicein.isBooked'),
columnFilter: false,
},
{
@ -41,7 +41,7 @@ const cols = computed(() => [
{
align: 'left',
name: 'supplierFk',
label: t('InvoiceIn.list.supplier'),
label: t('invoicein.list.supplier'),
columnFilter: {
component: 'select',
attrs: {
@ -55,16 +55,16 @@ const cols = computed(() => [
{
align: 'left',
name: 'supplierRef',
label: t('InvoiceIn.list.supplierRef'),
label: t('invoicein.list.supplierRef'),
},
{
align: 'left',
name: 'serial',
label: t('InvoiceIn.serial'),
label: t('invoicein.serial'),
},
{
align: 'left',
label: t('InvoiceIn.list.issued'),
label: t('invoicein.list.issued'),
name: 'issued',
component: null,
columnFilter: {
@ -74,7 +74,7 @@ const cols = computed(() => [
},
{
align: 'left',
label: t('InvoiceIn.list.dueDated'),
label: t('invoicein.list.dueDated'),
name: 'dueDated',
component: null,
columnFilter: {
@ -86,12 +86,12 @@ const cols = computed(() => [
{
align: 'left',
name: 'awbCode',
label: t('InvoiceIn.list.awb'),
label: t('invoicein.list.awb'),
},
{
align: 'left',
name: 'amount',
label: t('InvoiceIn.list.amount'),
label: t('invoicein.list.amount'),
format: ({ amount }) => toCurrency(amount),
cardVisible: true,
},
@ -165,24 +165,24 @@ const cols = computed(() => [
<VnSelect
v-model="data.supplierFk"
url="Suppliers"
:fields="['id', 'nickname']"
:fields="['id', 'nickname', 'name']"
:label="t('globals.supplier')"
option-value="id"
option-label="nickname"
:filter-options="['id', 'name']"
:filter-options="['id', 'name', 'nickname']"
:required="true"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }}, {{ scope.opt?.name }} </QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnInput
:label="t('InvoiceIn.list.supplierRef')"
:label="t('invoicein.list.supplierRef')"
v-model="data.supplierRef"
/>
<VnSelect
@ -194,7 +194,7 @@ const cols = computed(() => [
option-label="code"
:required="true"
/>
<VnInputDate :label="t('InvoiceIn.summary.issued')" v-model="data.issued" />
<VnInputDate :label="t('invoicein.summary.issued')" v-model="data.issued" />
</template>
</VnTable>
</template>

View File

@ -1,4 +1,4 @@
InvoiceIn:
invoicein:
serial: Serial
isBooked: Is booked
list:
@ -12,6 +12,26 @@ InvoiceIn:
amount: Amount
descriptor:
ticketList: Ticket list
descriptorMenu:
book: Book
unbook: Unbook
delete: Delete
clone: Clone
toBook: To book
toUnbook: To unbook
deleteInvoice: Delete invoice
invoiceDeleted: invoice deleted
cloneInvoice: Clone invoice
invoiceCloned: Invoice cloned
showAgriculturalPdf: Show agricultural receipt as PDF
sendAgriculturalPdf: Send agricultural receipt as PDF
checkSendInvoice: Are you sure you want to send it?
sendPdfInvoice: Send PDF invoice
createCorrective: Create rectificative invoice
correctiveInvoice: Rectificative invoice
originalInvoice: Original invoice
entry: Entry
emailEmpty: The email can't be empty
card:
client: Client
company: Company
@ -44,7 +64,8 @@ InvoiceIn:
country: Country
params:
search: Id or supplier name
account: Ledger account
correctingFk: Rectificative
correctedFk: Corrected
isBooked: Is booked
account: Ledger account
correctingFk: Rectificative

View File

@ -1,4 +1,4 @@
InvoiceIn:
invoicein:
serial: Serie
isBooked: Contabilizada
list:
@ -12,6 +12,26 @@ InvoiceIn:
amount: Importe
descriptor:
ticketList: Listado de tickets
descriptorMenu:
book: Asentar
unbook: Desasentar
delete: Eliminar
clone: Clonar
toBook: Contabilizar
toUnbook: Descontabilizar
deleteInvoice: Eliminar factura
invoiceDeleted: Factura eliminada
cloneInvoice: Clonar factura
invoiceCloned: Factura clonada
showAgriculturalPdf: Ver recibo agrícola como PDF
sendAgriculturalPdf: Enviar recibo agrícola como PDF
checkSendInvoice: ¿Estás seguro que quieres enviarlo?
sendPdfInvoice: Enviar factura a PDF
createCorrective: Crear factura rectificativa
correctiveInvoice: Factura rectificativa
originalInvoice: Factura origen
entry: Entrada
emailEmpty: El email no puede estar vacío
card:
client: Cliente
company: Empresa
@ -42,6 +62,7 @@ InvoiceIn:
country: País
params:
search: Id o nombre proveedor
correctedFk: Rectificada
account: Cuenta contable
correctingFk: Rectificativa
correctedFk: Rectificada

View File

@ -10,6 +10,7 @@ import { getUrl } from 'src/composables/getUrl';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue';
onMounted(async () => {
fetch();
@ -113,6 +114,9 @@ const ticketsColumns = ref([
<template #header="{ entity: { invoiceOut } }">
<div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div>
</template>
<template #menu="{ entity }">
<InvoiceOutDescriptorMenu :invoice-out-data="entity.invoiceOut" />
</template>
<template #body="{ entity: { invoiceOut } }">
<QCard class="vn-one">
<VnTitle :text="t('globals.pageTitles.basicData')" />

View File

@ -47,6 +47,7 @@ const states = ref();
:label="t('Amount')"
v-model="params.amount"
is-outlined
data-cy="InvoiceOutFilterAmountBtn"
/>
</QItemSection>
</QItem>

View File

@ -101,6 +101,7 @@ onMounted(async () => {
dense
outlined
rounded
data-cy="InvoiceOutGlobalClientSelect"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -125,6 +126,7 @@ onMounted(async () => {
dense
outlined
rounded
data-cy="InvoiceOutGlobalSerialSelect"
/>
<VnInputDate
v-model="formData.invoiceDate"
@ -135,6 +137,7 @@ onMounted(async () => {
v-model="formData.maxShipped"
:label="t('maxShipped')"
is-outlined
data-cy="InvoiceOutGlobalMaxShippedDate"
/>
<VnSelect
:label="t('company')"
@ -144,6 +147,7 @@ onMounted(async () => {
dense
outlined
rounded
data-cy="InvoiceOutGlobalCompanySelect"
/>
<VnSelect
:label="t('printer')"
@ -152,6 +156,7 @@ onMounted(async () => {
dense
outlined
rounded
data-cy="InvoiceOutGlobalPrinterSelect"
/>
</div>
<QBtn

View File

@ -126,6 +126,13 @@ const columns = computed(() => [
columnField: { component: null },
format: (row) => toDate(row.dued),
},
{
align: 'left',
name: 'customsAgentFk',
label: t('invoiceOutList.tableVisibleColumns.customsAgent'),
cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.customsAgentName),
},
{
align: 'right',
name: 'tableActions',
@ -190,6 +197,7 @@ watchEffect(selectedRows);
icon-right="cloud_download"
@click="downloadPdf()"
:disable="!hasSelectedCards"
data-cy="InvoiceOutDownloadPdfBtn"
>
<QTooltip>{{ t('downloadPdf') }}</QTooltip>
</QBtn>
@ -238,6 +246,7 @@ watchEffect(selectedRows);
v-model="data.ticketFk"
:label="t('globals.ticket')"
style="flex: 1"
data-cy="InvoiceOutCreateTicketinput"
/>
<div
@ -346,12 +355,13 @@ watchEffect(selectedRows);
<VnSelect
url="InvoiceOutSerials"
v-model="data.serial"
:label="t('InvoiceIn.serial')"
:label="t('invoicein.serial')"
:options="invoiceOutSerialsOptions"
option-label="description"
option-value="code"
option-filter
:expr-builder="exprBuilder"
data-cy="InvoiceOutCreateSerialSelect"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">

View File

@ -13,6 +13,7 @@ invoiceOutList:
invoiceOutSerial: Serial
ticket: Ticket
taxArea: Tax area
customsAgent: Custom Agent
DownloadPdf: Download PDF
InvoiceOutSummary: Summary
negativeBases:
@ -24,3 +25,15 @@ negativeBases:
hasToInvoice: Has to invoice
verifiedData: Verified data
commercial: Commercial
invoiceout:
params:
company: Company
country: Country
clientId: Client ID
clientSocialName: Client
taxableBase: Base
ticketFk: Ticket
isActive: Active
hasToInvoice: Has to invoice
hasVerifiedData: Verified data
workerName: Worker

View File

@ -15,6 +15,7 @@ invoiceOutList:
invoiceOutSerial: Serial
ticket: Ticket
taxArea: Area
customsAgent: Agente de aduanas
DownloadPdf: Descargar PDF
InvoiceOutSummary: Resumen
negativeBases:
@ -27,3 +28,15 @@ negativeBases:
hasToInvoice: Debe facturar
verifiedData: Datos verificados
commercial: Comercial
invoiceout:
params:
company: Empresa
country: País
clientId: ID del cliente
clientSocialName: Cliente
taxableBase: Base
ticketFk: Ticket
isActive: Activo
hasToInvoice: Debe facturar
hasVerifiedData: Datos verificados
workerName: Comercial

View File

@ -52,6 +52,7 @@ const entityId = computed(() => {
:fields="['id', 'name']"
sort-by="name ASC"
hide-selected
data-cy="AddGenusSelectDialog"
>
<template #form>
<CreateGenusForm
@ -68,6 +69,7 @@ const entityId = computed(() => {
:fields="['id', 'name']"
sort-by="name ASC"
hide-selected
data-cy="AddSpeciesSelectDialog"
>
<template #form>
<CreateSpecieForm

View File

@ -6,17 +6,16 @@ import { useI18n } from 'vue-i18n';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import RegularizeStockForm from 'components/RegularizeStockForm.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import useCardDescription from 'src/composables/useCardDescription';
import axios from 'axios';
import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'src/composables/useArrayData';
import { cloneItem } from 'src/pages/Item/composables/cloneItem';
import ItemDescriptorMenu from './ItemDescriptorMenu.vue';
const $props = defineProps({
id: {
type: [Number, String],
type: Number,
required: false,
default: null,
},
@ -29,7 +28,7 @@ const $props = defineProps({
default: null,
},
saleFk: {
type: [Number, String],
type: Number,
default: null,
},
warehouseFk: {
@ -38,7 +37,6 @@ const $props = defineProps({
},
});
const { openCloneDialog } = cloneItem();
const route = useRoute();
const { t } = useI18n();
const warehouseConfig = ref(null);
@ -46,7 +44,6 @@ const entityId = computed(() => {
return $props.id || route.params.id;
});
const regularizeStockFormDialog = ref(null);
const mounted = ref();
const arrayDataStock = useArrayData('descriptorStock', {
@ -61,14 +58,14 @@ onMounted(async () => {
const data = ref(useCardDescription());
const setData = async (entity) => {
if (!entity) return;
data.value = useCardDescription(entity?.name, entity?.id);
data.value = useCardDescription(entity.name, entity.id);
await updateStock();
};
const getItemConfigs = async () => {
const { data } = await axios.get('ItemConfigs/findOne');
if (!data) return;
return (warehouseConfig.value = data.warehouseFk);
warehouseConfig.value = data.warehouseFk;
};
const updateStock = async () => {
if (!mounted.value) return;
@ -89,10 +86,6 @@ const updateStock = async () => {
if (storeData?.itemFk == entityId.value) return;
await stock.fetch({});
};
const openRegularizeStockForm = () => {
regularizeStockFormDialog.value.show();
};
</script>
<template>
@ -105,24 +98,12 @@ const openRegularizeStockForm = () => {
:url="`Items/${entityId}/getCard`"
@on-fetch="setData"
>
<template #menu="{}">
<QItem v-ripple clickable @click="openRegularizeStockForm()">
<QItemSection>
{{ t('Regularize stock') }}
<QDialog ref="regularizeStockFormDialog">
<RegularizeStockForm
:item-fk="entityId"
:warehouse-fk="warehouseFk"
@on-data-saved="updateStock()"
/>
</QDialog>
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openCloneDialog(entityId)">
<QItemSection>
{{ t('globals.clone') }}
</QItemSection>
</QItem>
<template #menu>
<ItemDescriptorMenu
:entity-id="entityId"
:warehouse-fk="warehouseFk"
@regularized="updateStock"
/>
</template>
<template #before>
<ItemDescriptorImage
@ -191,7 +172,6 @@ const openRegularizeStockForm = () => {
<i18n>
es:
Regularize stock: Regularizar stock
Inactive article: Artículo inactivo
</i18n>

View File

@ -131,7 +131,6 @@ const handlePhotoUpdated = (evt = false) => {
<i18n>
es:
Regularize stock: Regularizar stock
All it's properties will be copied: Todas sus propiedades serán copiadas
Do you want to clone this item?: ¿Desea clonar este artículo?
warehouseText: Calculado sobre el almacén de { warehouseName }

View File

@ -0,0 +1,44 @@
<script setup>
import RegularizeStockForm from 'components/RegularizeStockForm.vue';
import { cloneItem } from 'src/pages/Item/composables/cloneItem';
const { openCloneDialog } = cloneItem();
defineProps({
entityId: {
type: Number,
default: null,
},
warehouseFk: {
type: Number,
default: null,
},
});
defineEmits(['regularized']);
</script>
<template>
<QItem v-ripple clickable @click="$refs.regularizeStockFormDialog.show()">
<QItemSection>
{{ $t('item.regularizeStock') }}
<QDialog ref="regularizeStockFormDialog">
<RegularizeStockForm
:item-fk="entityId"
:warehouse-fk="warehouseFk"
@on-data-saved="$emit('regularized')"
/>
</QDialog>
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openCloneDialog(entityId)">
<QItemSection>
{{ $t('globals.clone') }}
</QItemSection>
</QItem>
</template>
<style lang="scss" scoped>
.weekdaysBtn {
margin: 1%;
}
</style>

View File

@ -233,7 +233,7 @@ async function updateWarehouse(warehouseFk) {
</div>
</template>
</VnSubToolbar>
<QPage class="column items-center q-pa-md">
<QPage class="column items-center">
<QTable
:rows="itemBalances"
:columns="columns"

View File

@ -10,21 +10,12 @@ import { dashIfEmpty } from 'src/filters';
import { toCurrency } from 'filters/index';
import { useArrayData } from 'composables/useArrayData';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import axios from 'axios';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
const { t } = useI18n();
const route = useRoute();
const from = ref();
const to = ref();
const hideInventory = ref(true);
const inventorySupplierFk = ref();
async function getInventorySupplier() {
inventorySupplierFk.value = (
await axios.get(`InventoryConfigs`)
)?.data[0]?.supplierFk;
}
const exprBuilder = (param, value) => {
switch (param) {
@ -49,10 +40,6 @@ const where = {
itemFk: route.params.id,
};
if (hideInventory.value) {
where.supplierFk = { neq: inventorySupplierFk };
}
const arrayData = useArrayData('ItemLastEntries', {
url: 'Items/lastEntriesFilter',
order: ['landed DESC', 'buyFk DESC'],
@ -110,7 +97,7 @@ const columns = computed(() => [
format: (val) => dashIfEmpty(val),
},
{
label: t('shelvings.packing'),
label: 'Packing',
name: 'packing',
field: 'packing',
align: 'center',
@ -182,15 +169,11 @@ const updateFilter = async () => {
const userFilter = arrayData.store.userFilter.where;
userFilter.landed = filter;
if (hideInventory.value) userFilter.supplierFk = { neq: inventorySupplierFk };
else delete userFilter.supplierFk;
await fetchItemLastEntries();
};
onMounted(async () => {
await getInventorySupplier();
const _from = Date.vnNew();
_from.setDate(_from.getDate() - 75);
from.value = getDate(_from, 'from');
@ -200,12 +183,16 @@ onMounted(async () => {
updateFilter();
watch([from, to, hideInventory], ([nFrom, nTo], [oFrom, oTo]) => {
watch([from, to], ([nFrom, nTo], [oFrom, oTo]) => {
if (nFrom && nFrom != oFrom) nFrom = getDate(new Date(nFrom), 'from');
if (nTo && nTo != oTo) nTo = getDate(new Date(nTo), 'to');
updateFilter();
});
});
function getBadgeClass(groupingMode, expectedGrouping) {
return groupingMode === expectedGrouping ? 'accent-badge' : 'simple-badge';
}
</script>
<template>
<VnSubToolbar>
@ -224,13 +211,6 @@ onMounted(async () => {
class="q-mr-lg"
data-cy="to"
/>
<QCheckbox
:label="t('Hide inventory supplier')"
v-model="hideInventory"
dense
class="q-mr-lg"
data-cy="hideInventory"
/>
</template>
</VnSubToolbar>
<QPage class="column items-center q-pa-xd">
@ -249,6 +229,11 @@ onMounted(async () => {
/>
</QTd>
</template>
<template #body-cell-warehouse="{ row }">
<QTd>
<span>{{ row.warehouse }}</span>
</QTd>
</template>
<template #body-cell-date="{ row }">
<QTd class="text-center">
<VnDateBadge :date="row.landed" />
@ -262,32 +247,37 @@ onMounted(async () => {
</div>
</QTd>
</template>
<template #body-cell-pvp="{ value }">
<QTd @click.stop class="text-center">
<span> {{ value }}</span>
<QTooltip> {{ t('lastEntries.grouping') }}/Packing </QTooltip></QTd
>
</template>
<template #body-cell-printedStickers="{ row }">
<QTd @click.stop class="text-center">
<span style="color: var(--vn-label-color)">
{{ row.printedStickers }}</span
>
</QTd>
</template>
<template #body-cell-packing="{ row }">
<QTd @click.stop>
<QBadge
class="center-content"
:class="getBadgeClass(row.groupingMode, 'packing')"
rounded
:color="row.groupingMode == 'packing' ? 'grey-13' : 'black'"
>
{{ dashIfEmpty(row.packing) }}
<QTooltip>{{ t('lastEntries.packing') }}</QTooltip>
<QTooltip>Packing</QTooltip>
</QBadge>
</QTd>
</template>
<template #body-cell-pvp="{ value }">
<QTd @click.stop class="text-center">
<span> {{ value }}</span>
<QTooltip>
{{ t('lastEntries.grouping') }}/{{ t('lastEntries.packing') }}
</QTooltip></QTd
>
</template>
<template #body-cell-grouping="{ row }">
<QTd @click.stop>
<QBadge
class="center-content"
:class="getBadgeClass(row.groupingMode, 'grouping')"
rounded
:color="row.groupingMode == 'grouping' ? 'grey-13' : 'black'"
>
{{ dashIfEmpty(row.grouping) }}
<QTooltip>{{ t('lastEntries.grouping') }}</QTooltip>
@ -315,13 +305,16 @@ onMounted(async () => {
</template>
<template #body-cell-supplier="{ row }">
<QTd @click.stop>
<div class="full-width flex justify-center">
<SupplierDescriptorProxy
:id="row.supplierFk"
class="q-ma-none"
<div class="full-width flex justify-left">
<QBadge
:class="
row.isInventorySupplier ? 'bg-vn-page' : 'transparent'
"
dense
/>
<span class="link">{{ row.supplier }}</span>
>
<SupplierDescriptorProxy :id="row.supplierFk" />
<span class="link">{{ row.supplier }}</span>
</QBadge>
</div>
</QTd>
</template>
@ -349,4 +342,13 @@ onMounted(async () => {
background-color: red;
}
}
.accent-badge {
background-color: var(--vn-label-color);
color: var(--vn-text-color-contrast);
}
.simple-badge {
background-color: transparent;
color: var(--vn-text-color);
font-size: 14px;
}
</style>

View File

@ -8,6 +8,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import ItemDescriptorMenu from './ItemDescriptorMenu.vue';
const $props = defineProps({
id: {
@ -43,10 +44,13 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
<template #header="{ entity: { item } }">
{{ item.id }} - {{ item.name }}
</template>
<template #menu>
<ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" />
</template>
<template #body="{ entity: { item, tags, visible, available, botanical } }">
<QCard class="vn-one photo">
<ItemDescriptorImage
:entity-id="Number(entityId)"
:entity-id="entityId"
:visible="visible"
:available="available"
:show-edit-button="false"
@ -89,7 +93,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`;
<QCard class="vn-one">
<VnTitle
:url="getUrl(entityId, 'basic-data')"
:text="t('item.summary.basicData')"
:text="t('item.summary.otherData')"
/>
<VnLv
:label="t('item.summary.intrastatCode')"

Some files were not shown because too many files have changed in this diff Show More