#6273 create freelance form option #347

Merged
jorgep merged 21 commits from 6273-createFreelanceFormOpt into dev 2024-06-10 13:35:00 +00:00
18 changed files with 214 additions and 124 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto
- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa
## [2418.01]

View File

@ -0,0 +1,6 @@
<script setup>
const model = defineModel({ type: Boolean, required: true });
</script>
<template>
<QRadio v-model="model" v-bind="$attrs" dense :dark="true" class="q-mr-sm" />
</template>

View File

@ -18,7 +18,7 @@ onMounted(() => {
const observer = new MutationObserver(
() =>
(hasContent.value =
actions.value.childNodes.length + data.value.childNodes.length)
actions.value?.childNodes?.length + data.value?.childNodes?.length)
Review

Arregla un error de consola

Arregla un error de consola
);
if (actions.value) observer.observe(actions.value, opts);
if (data.value) observer.observe(data.value, opts);

View File

@ -34,7 +34,7 @@ const entryFilter = {
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'],
fields: ['id', 'landed', 'shipped', 'agencyModeFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
@ -125,10 +125,8 @@ watch;
:label="t('entry.descriptor.agency')"
:value="entity.travel?.agency?.name"
/>
<VnLv
:label="t('entry.descriptor.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('entry.descriptor.warehouseOut')"
:value="entity.travel?.warehouseOut?.name"

View File

@ -221,10 +221,7 @@ const fetchEntryBuys = async () => {
:value="entry.travel.agency.name"
/>
<VnLv
:label="t('entry.summary.travelShipped')"
:value="toDate(entry.travel.shipped)"
/>
<VnLv :label="t('shipped')" :value="toDate(entry.travel.shipped)" />
<VnLv
:label="t('entry.summary.travelWarehouseOut')"
@ -236,10 +233,7 @@ const fetchEntryBuys = async () => {
v-model="entry.travel.isDelivered"
:disable="true"
/>
<VnLv
:label="t('entry.summary.travelLanded')"
:value="toDate(entry.travel.landed)"
/>
<VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" />
<VnLv
:label="t('entry.summary.travelWarehouseIn')"

View File

@ -78,10 +78,7 @@ onMounted(async () => {
</QIcon>
</template>
<template #list-items>
<VnLv
:label="t('entry.list.landed')"
:value="toDate(row.landed)"
/>
<VnLv :label="t('landed')" :value="toDate(row.landed)" />
<VnLv
:label="t('entry.list.booked')"
:value="!!row.isBooked"

View File

@ -6,3 +6,5 @@ entryFilter:
filter:
search: General search
reference: Reference
landed: Landed
shipped: Shipped

View File

@ -8,3 +8,6 @@ entryFilter:
filter:
search: Búsqueda general
reference: Referencia
landed: F. llegada
shipped: F. salida

View File

@ -19,7 +19,8 @@ const { t } = useI18n();
const dms = ref({});
const route = useRoute();
const editDownloadDisabled = ref(false);
const invoiceIn = computed(() => useArrayData().store.data);
const arrayData = useArrayData();
Review

Lanzaba un error debido a un cambio en arrayData. Así se corrige.

Lanzaba un error debido a un cambio en arrayData. Así se corrige.
const invoiceIn = computed(() => arrayData.store.data);
const userConfig = ref(null);
const invoiceId = computed(() => +route.params.id);

View File

@ -12,7 +12,7 @@ const { push, currentRoute } = useRouter();
const { t } = useI18n();
const invoiceId = +currentRoute.value.params.id;
const arrayData = useArrayData(currentRoute.value.meta.moduleName);
const arrayData = useArrayData();
Review

Lanzaba un error debido a un cambio en arrayData. Así se corrige.

Lanzaba un error debido a un cambio en arrayData. Así se corrige.
const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref();
const filter = {

View File

@ -27,9 +27,9 @@ const quasar = useQuasar();
const { hasAny } = useRole();
const { t } = useI18n();
const { openReport, sendEmail } = usePrintService();
const { store } = useArrayData(currentRoute.value.meta.moduleName);
const arrayData = useArrayData();
Review

Lanzaba un error debido a un cambio en arrayData. Así se corrige.

Lanzaba un error debido a un cambio en arrayData. Así se corrige.
const invoiceIn = computed(() => store.data);
const invoiceIn = computed(() => arrayData.store.data);
const cardDescriptorRef = ref();
const correctionDialogRef = ref();
const entityId = computed(() => $props.id || +currentRoute.value.params.id);
@ -184,7 +184,7 @@ async function toUnbook() {
: t('isNotLinked', { bookEntry });
quasar.notify({ type, message });
if (!isLinked) store.data.isBooked = false;
if (!isLinked) arrayData.store.data.isBooked = false;
}
async function deleteInvoice() {

View File

@ -11,7 +11,8 @@ import { useArrayData } from 'src/composables/useArrayData';
const { t } = useI18n();
const route = useRoute();
const currency = computed(() => useArrayData().store.data?.currency?.code);
const arrayData = useArrayData();
Review

Lanzaba un error debido a un cambio en arrayData. Así se corrige.

Lanzaba un error debido a un cambio en arrayData. Así se corrige.
const currency = computed(() => arrayData.store.data?.currency?.code);
const invoceInIntrastat = ref([]);
const rowsSelected = ref([]);
const countries = ref([]);

View File

@ -14,9 +14,10 @@ import VnTitle from 'src/components/common/VnTitle.vue';
const props = defineProps({ id: { type: [Number, String], default: 0 } });
const { t } = useI18n();
const route = useRoute();
const arrayData = useArrayData();
const entityId = computed(() => props.id || +route.params.id);
const invoiceIn = computed(() => useArrayData().store.data);
const invoiceIn = computed(() => arrayData.store.data);
Review

Lanzaba un error debido a un cambio en arrayData. Así se corrige.

Lanzaba un error debido a un cambio en arrayData. Así se corrige.
const currency = computed(() => invoiceIn.value?.currency?.code);
const invoiceInUrl = ref();
const amountsNotMatch = ref(null);

View File

@ -1,59 +1,29 @@
<script setup>
import { onMounted, ref } from 'vue';
import { onBeforeMount, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import axios from 'axios';
import { useUserConfig } from 'src/composables/useUserConfig';
import VnRow from 'components/ui/VnRow.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useUserConfig } from 'src/composables/useUserConfig';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import VnRadio from 'src/components/common/VnRadio.vue';
const { t } = useI18n();
const workerConfigFilter = {
field: ['payMethodFk'],
};
const newWorkerForm = ref({
companyFk: null,
payMethodFk: null,
firstName: null,
lastNames: null,
birth: null,
fi: null,
code: null,
phone: null,
postcode: null,
provinceFk: null,
city: null,
street: null,
name: null,
email: null,
bossFk: null,
iban: null,
bankEntityFk: null,
});
const companiesOptions = ref([]);
const workersOptions = ref([]);
const payMethodsOptions = ref([]);
const bankEntitiesOptions = ref([]);
const postcodesOptions = ref([]);
const onFetchWorkerConfig = (workerConfig) => {
newWorkerForm.value.payMethodFk = workerConfig.payMethodFk;
};
const onBankEntityCreated = (data) => {
bankEntitiesOptions.value.push(data);
};
const formData = ref({ isFreelance: false });
const defaultPayMethod = ref(0);
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
@ -63,20 +33,18 @@ function handleLocation(data, location) {
data.countryFk = countryFk;
}
onMounted(async () => {
onBeforeMount(async () => {
const userInfo = await useUserConfig().fetch();
newWorkerForm.value = { companyFk: userInfo.companyFk };
formData.value.companyFk = userInfo.companyFk;
const { data } = await axios.get('WorkerConfigs/findOne', {
params: { field: ['payMethodFk'] },
});
defaultPayMethod.value = data.payMethodFk;
formData.value.payMethodFk = defaultPayMethod.value;
});
</script>
<template>
<FetchData
url="WorkerConfigs/findOne"
@on-fetch="(data) => onFetchWorkerConfig(data)"
:filter="workerConfigFilter"
auto-load
/>
<FetchData
url="Companies"
@on-fetch="(data) => (companiesOptions = data)"
@ -97,16 +65,31 @@ onMounted(async () => {
@on-fetch="(data) => (bankEntitiesOptions = data)"
auto-load
/>
<QPage>
<VnSubToolbar />
<VnSubToolbar>
<template #st-data>
<VnRadio
v-model="formData.isFreelance"
:val="false"
:label="`${t('Internal')}`"
@update:model-value="formData.payMethodFk = defaultPayMethod"
/>
<VnRadio
v-model="formData.isFreelance"
:val="true"
:label="`${t('External')}`"
@update:model-value="delete formData.payMethodFk"
jorgep marked this conversation as resolved Outdated

eliminar valor 0, no se envia esa propiedad si no tiene valor

eliminar valor 0, no se envia esa propiedad si no tiene valor
/>
</template>
</VnSubToolbar>
<FormModel
url-create="Workers/new"
model="worker"
:form-initial-data="newWorkerForm"
:form-initial-data="formData"
@on-data-saved="({ id }) => $router.push({ path: `/worker/${id}` })"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<VnRow>
<VnInput
v-model="data.firstName"
:label="t('worker.create.name')"
@ -117,47 +100,13 @@ onMounted(async () => {
:label="t('worker.create.lastName')"
:rules="validate('Worker.lastNames')"
/>
<VnInputDate
v-model="data.birth"
:label="t('worker.create.birth')"
:rules="validate('Worker.birth')"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
v-model="data.fi"
:label="t('worker.create.fi')"
:rules="validate('Worker.fi')"
/>
<VnInput
v-model="data.code"
:label="t('worker.create.code')"
:rules="validate('Worker.code')"
/>
<VnInput
v-model="data.phone"
:label="t('worker.create.phone')"
:rules="validate('Worker.phone')"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnLocation
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
>
</VnLocation>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
:label="t('worker.create.street')"
v-model="data.street"
:rules="validate('Worker.street')"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnRow>
<VnInput
v-model="data.name"
:label="t('worker.create.webUser')"
@ -169,7 +118,7 @@ onMounted(async () => {
:rules="validate('Worker.email')"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnRow>
<VnSelect
:label="t('worker.create.company')"
v-model="data.companyFk"
@ -201,7 +150,45 @@ onMounted(async () => {
</template>
</VnSelect>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnRow>
<VnInput
v-model="data.fi"
:label="t('worker.create.fi')"
:rules="validate('Worker.fi')"
/>
<VnInputDate
v-model="data.birth"
:label="t('worker.create.birth')"
:rules="validate('Worker.birth')"
:disable="formData.isFreelance"
/>
<VnInput
v-model="data.phone"
:label="t('worker.create.phone')"
:rules="validate('Worker.phone')"
:disable="formData.isFreelance"
/>
</VnRow>
<VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
:disable="formData.isFreelance"
>
</VnLocation>
</VnRow>
<VnRow>
<VnInput
:label="t('worker.create.street')"
v-model="data.street"
:rules="validate('Worker.street')"
:disable="formData.isFreelance"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('worker.create.payMethods')"
v-model="data.payMethodFk"
@ -211,11 +198,14 @@ onMounted(async () => {
map-options
hide-selected
:rules="validate('Worker.payMethodFk')"
:disable="formData.isFreelance"
@update:model-value="(val) => !val && delete formData.payMethodFk"
/>
<VnInput
v-model="data.iban"
:label="t('worker.create.iban')"
:rules="validate('Worker.iban')"
:disable="formData.isFreelance"
>
<template #append>
<QIcon name="info" class="cursor-info">
@ -232,10 +222,11 @@ onMounted(async () => {
hide-selected
:roles-allowed-to-create="['salesAssistant', 'hr']"
:rules="validate('Worker.bankEntity')"
:disable="formData.isFreelance"
>
<template #form>
<CreateBankEntityForm
@on-data-saved="onBankEntityCreated($event)"
@on-data-saved="(data) => bankEntitiesOptions.push(data)"
/>
</template>
<template #option="scope">

View File

@ -1,3 +1,5 @@
Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del trabajador
Locker: Taquilla
Internal: Interno
External: Externo

View File

@ -0,0 +1,59 @@
describe('WorkerCreate', () => {
const externalRadio = '.q-toolbar .q-radio:nth-child(2)';
const notification = '.q-notification__message';
const developerBossId = 120;
const internal = {
Fi: { val: '78457139E' },
'Web user': { val: 'manolo' },
Name: { val: 'Manolo' },
'Last name': { val: 'Hurtado' },
'Personal email': { val: 'manolo@mydomain.com' },
Street: { val: 'S/ DEFAULTWORKERSTREET' },
Location: { val: 1, type: 'select' },
Phone: { val: '123456789' },
'Worker code': { val: 'DWW' },
Boss: { val: developerBossId, type: 'select' },
Birth: { val: '2022-12-11T23:00:00.000Z', type: 'date', day: 11 },
};
const external = {
Fi: { val: 'Z4531219V' },
'Web user': { val: 'pepe' },
Name: { val: 'PEPE' },
'Last name': { val: 'GARCIA' },
'Personal email': { val: 'pepe@gmail.com' },
'Worker code': { val: 'PG' },
Boss: { val: developerBossId, type: 'select' },
};
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('hr');
cy.visit('/#/worker/create');
});
it('should throw an error if a pay method has not been selected', () => {
cy.fillInForm(internal);
cy.saveCard();
cy.get(notification).should(
'contains.text',
'That payment method requires an IBAN'
);
});
it('should create an internal', () => {
cy.fillInForm({
...internal,
'Pay method': { val: 'PayMethod one', type: 'select' },
});
cy.saveCard();
cy.get(notification).should('contains.text', 'Data created');
});
it('should create an external', () => {
cy.get(externalRadio).click();
cy.fillInForm(external);
cy.saveCard();
cy.get(notification).should('contains.text', 'Data created');
});
});

View File

@ -8,13 +8,13 @@ describe('WorkerList', () => {
});
it('should load workers', () => {
cy.get(workerFieldNames).eq(0).should('have.text', 'jessicajones');
cy.get(workerFieldNames).eq(1).should('have.text', 'brucebanner');
cy.get(workerFieldNames).eq(2).should('have.text', 'charlesxavier');
cy.get(workerFieldNames).eq(2).should('have.text', 'jessicajones');
Review

Al crear 2 trabajadores más en el test anterior, la posición de estos cambia.

Al crear 2 trabajadores más en el test anterior, la posición de estos cambia.
cy.get(workerFieldNames).eq(3).should('have.text', 'brucebanner');
cy.get(workerFieldNames).eq(4).should('have.text', 'charlesxavier');
});
it('should open the worker summary', () => {
cy.openListSummary(0);
cy.openListSummary(2);
cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones');
cy.get('.summary .header-link')
.eq(0)

View File

@ -86,6 +86,36 @@ Cypress.Commands.add('selectOption', (selector, option) => {
cy.get('.q-menu .q-item').contains(option).click();
});
Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => {
Review

Rellena los campos de un formulario.

Rellena los campos de un formulario.
const days = '.q-date__calendar-days .q-date__calendar-item--in';
cy.waitForElement('.q-form > .q-card');
cy.get(`${form} input`).each(([el]) => {
cy.wrap(el)
.invoke('attr', 'aria-label')
.then((ariaLabel) => {
const field = obj[ariaLabel];
if (!field) return;
const { type, val, day } = field;
switch (type) {
case 'select':
cy.wrap(el).type(val);
cy.get('.q-menu .q-item').contains(val).click();
break;
case 'date':
cy.wrap(el).click();
cy.get(days)
.eq(day ? day - 1 : 0)
.click();
break;
default:
cy.wrap(el).type(val);
break;
}
});
});
});
Cypress.Commands.add('checkOption', (selector) => {
cy.get(selector).find('.q-checkbox__inner').click();
});
@ -198,11 +228,15 @@ Cypress.Commands.add('closeSideMenu', (element) => {
Cypress.Commands.add('clearSearchbar', (element) => {
if (element) cy.waitForElement(element);
cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').clear();
cy.get(
'#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input'
).clear();
});
Cypress.Commands.add('writeSearchbar', (value) => {
cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').type(value);
cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').type(
value
);
});
Cypress.Commands.add('validateContent', (selector, expectedValue) => {
cy.get(selector).should('have.text', expectedValue);