Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2034-postcode_autocomplete
This commit is contained in:
commit
f16553ecba
|
@ -10,8 +10,7 @@ module.exports = Self => {
|
|||
type: 'Number',
|
||||
description: 'The document id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: 'The warehouse id'
|
||||
|
@ -44,9 +43,9 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.updateFile = async(ctx, id, warehouseId, companyId,
|
||||
dmsTypeId, reference, description, hasFileAttached, options) => {
|
||||
Self.updateFile = async(ctx, id, options) => {
|
||||
const models = Self.app.models;
|
||||
const args = ctx.args;
|
||||
|
||||
let tx;
|
||||
let myOptions = {};
|
||||
|
@ -60,20 +59,20 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dmsTypeId);
|
||||
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId);
|
||||
if (!hasWriteRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
const dms = await Self.findById(id, null, myOptions);
|
||||
await dms.updateAttributes({
|
||||
dmsTypeFk: dmsTypeId,
|
||||
companyFk: companyId,
|
||||
warehouseFk: warehouseId,
|
||||
reference: reference,
|
||||
description: description
|
||||
dmsTypeFk: args.dmsTypeId,
|
||||
companyFk: args.companyId,
|
||||
warehouseFk: args.warehouseId,
|
||||
reference: args.reference,
|
||||
description: args.description
|
||||
}, myOptions);
|
||||
|
||||
if (hasFileAttached)
|
||||
if (args.hasFileAttached)
|
||||
await uploadNewFile(ctx, dms, myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
|
|
@ -1903,11 +1903,11 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c
|
|||
VALUES
|
||||
(1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'),
|
||||
(2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'),
|
||||
(3, 'Laboral', 'laboral', NULL, NULL, 'hhrrData'),
|
||||
(3, 'Laboral', 'laboral', 37, 37, 'hhrrData'),
|
||||
(4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'),
|
||||
(5, 'Otros', 'otros', 1, 1, 'miscellaneous'),
|
||||
(6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'),
|
||||
(7, 'IAE Clientes', 'IAE_Clientes', NULL, NULL, 'economicActivitiesTax'),
|
||||
(7, 'IAE Clientes', 'IAE_Clientes', 1, 1, 'economicActivitiesTax'),
|
||||
(8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'),
|
||||
(9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'),
|
||||
(10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'),
|
||||
|
|
|
@ -9,6 +9,7 @@ export default {
|
|||
invoiceOutButton: '.modules-menu > li[ui-sref="invoiceOut.index"]',
|
||||
claimsButton: '.modules-menu > li[ui-sref="claim.index"]',
|
||||
returnToModuleIndexButton: 'a[ui-sref="order.index"]',
|
||||
homeButton: 'vn-topbar > div.side.start > a',
|
||||
userMenuButton: '#user',
|
||||
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
|
||||
userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
|
||||
|
@ -192,7 +193,7 @@ export default {
|
|||
},
|
||||
dms: {
|
||||
deleteFileButton: 'vn-client-dms-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
|
||||
firstDocWorker: 'vn-client-dms-index vn-td:nth-child(8) > span',
|
||||
firstDocWorker: 'vn-client-dms-index vn-td:nth-child(7) > span',
|
||||
firstDocWorkerDescriptor: '.vn-popover.shown vn-worker-descriptor',
|
||||
acceptDeleteButton: '.vn-confirm.shown button[response="accept"]'
|
||||
},
|
||||
|
@ -630,6 +631,16 @@ export default {
|
|||
createButton: 'button[type=submit]',
|
||||
cancelButton: 'vn-button[href="#!/client/index"]'
|
||||
},
|
||||
orderSummary: {
|
||||
header: 'vn-order-summary h5',
|
||||
id: 'vn-order-summary vn-one:nth-child(1) > vn-label-value:nth-child(1) span',
|
||||
alias: 'vn-order-summary vn-one:nth-child(1) > vn-label-value:nth-child(2) span',
|
||||
consignee: 'vn-order-summary vn-one:nth-child(2) > vn-label-value:nth-child(3) span',
|
||||
subtotal: 'vn-order-summary vn-one.taxes > p:nth-child(1)',
|
||||
vat: 'vn-order-summary vn-one.taxes > p:nth-child(2)',
|
||||
total: 'vn-order-summary vn-one.taxes > p:nth-child(3)',
|
||||
sale: 'vn-order-summary vn-tbody > vn-tr',
|
||||
},
|
||||
orderCatalog: {
|
||||
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
|
||||
type: 'vn-autocomplete[data="$ctrl.itemTypes"]',
|
||||
|
@ -700,6 +711,17 @@ export default {
|
|||
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',
|
||||
confirmButton: '.vn-confirm.shown button[response="accept"]'
|
||||
},
|
||||
workerSummary: {
|
||||
header: 'vn-worker-summary h5',
|
||||
id: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(2) > section > span',
|
||||
email: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(3) > section > span',
|
||||
department: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(4) > section > span',
|
||||
userId: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(2) > section > span',
|
||||
userName: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(3) > section > span',
|
||||
role: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(4) > section > span',
|
||||
extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span',
|
||||
|
||||
},
|
||||
workerBasicData: {
|
||||
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
|
||||
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
|
||||
|
@ -783,10 +805,25 @@ export default {
|
|||
ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)',
|
||||
ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)'
|
||||
},
|
||||
travelBasicDada: {
|
||||
reference: 'vn-travel-basic-data vn-textfield[ng-model="$ctrl.travel.ref"]',
|
||||
agency: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]',
|
||||
shippingDate: 'vn-travel-basic-data vn-date-picker[ng-model="$ctrl.travel.shipped"]',
|
||||
deliveryDate: 'vn-travel-basic-data vn-date-picker[ng-model="$ctrl.travel.landed"]',
|
||||
outputWarehouse: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]',
|
||||
inputWarehouse: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]',
|
||||
delivered: 'vn-travel-basic-data vn-check[ng-model="$ctrl.travel.isDelivered"]',
|
||||
received: 'vn-travel-basic-data vn-check[ng-model="$ctrl.travel.isReceived"]',
|
||||
save: 'vn-travel-basic-data vn-submit[label="Save"]',
|
||||
undoChanges: 'vn-travel-basic-data vn-button[label="Undo changes"]'
|
||||
},
|
||||
travelLog: {
|
||||
firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(1) > div'
|
||||
},
|
||||
travelThermograph: {
|
||||
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',
|
||||
thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]',
|
||||
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="cloud_upload"]',
|
||||
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="attach_file"]',
|
||||
createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr',
|
||||
upload: 'vn-travel-thermograph-create button[type=submit]'
|
||||
},
|
||||
|
@ -806,5 +843,10 @@ export default {
|
|||
header: 'vn-entry-summary > vn-card > h5',
|
||||
reference: 'vn-entry-summary vn-label-value[label="Reference"]',
|
||||
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
|
||||
},
|
||||
entryDescriptor: {
|
||||
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(3) span',
|
||||
travelsQuicklink: 'vn-entry-descriptor vn-quick-links > a:nth-child(1)',
|
||||
entriesQuicklink: 'vn-entry-descriptor vn-quick-links > a:nth-child(2)'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,6 +16,12 @@ describe('Client Add notes path', () => {
|
|||
await browser.close();
|
||||
});
|
||||
|
||||
it(`should reach the notes index`, async() => {
|
||||
let url = await page.expectURL('/note');
|
||||
|
||||
expect(url).toBe(true);
|
||||
});
|
||||
|
||||
it(`should click on the add note button`, async() => {
|
||||
await page.waitToClick(selectors.clientNotes.addNoteFloatButton);
|
||||
let url = await page.expectURL('/note/create');
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Worker summary path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('employee', 'worker');
|
||||
await page.accessToSearchResult('agencyNick');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should reach the employee summary section', async() => {
|
||||
const url = await page.expectURL('#!/worker/3/summary');
|
||||
|
||||
expect(url).toBe(true);
|
||||
});
|
||||
|
||||
it('should check the summary contains the name and userName on the header', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.header, 'innerText');
|
||||
|
||||
expect(result).toEqual('agency agency');
|
||||
});
|
||||
|
||||
it('should check the summary contains the basic data id', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.id, 'innerText');
|
||||
|
||||
expect(result).toEqual('3');
|
||||
});
|
||||
|
||||
it('should check the summary contains the basic data email', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.email, 'innerText');
|
||||
|
||||
expect(result).toEqual('agency@verdnatura.es');
|
||||
});
|
||||
|
||||
it('should check the summary contains the basic data department', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.department, 'innerText');
|
||||
|
||||
expect(result).toEqual('CAMARA');
|
||||
});
|
||||
|
||||
it('should check the summary contains the user data id', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.userId, 'innerText');
|
||||
|
||||
expect(result).toEqual('3');
|
||||
});
|
||||
|
||||
it('should check the summary contains the user data name', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.userName, 'innerText');
|
||||
|
||||
expect(result).toEqual('agency');
|
||||
});
|
||||
|
||||
it('should check the summary contains the user data role', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.role, 'innerText');
|
||||
|
||||
expect(result).toEqual('agency');
|
||||
});
|
||||
|
||||
it('should check the summary contains the user data extension', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerSummary.extension, 'innerText');
|
||||
|
||||
expect(result).toEqual('1101');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Order summary path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('employee', 'order');
|
||||
await page.accessToSearchResult('16');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should reach the order summary section', async() => {
|
||||
const url = await page.expectURL('#!/order/16/summary');
|
||||
|
||||
expect(url).toBe(true);
|
||||
});
|
||||
|
||||
it('should check the summary contains the order id', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.orderSummary.id, 'innerText');
|
||||
|
||||
expect(result).toEqual('16');
|
||||
});
|
||||
|
||||
it('should check the summary contains the order alias', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.orderSummary.alias, 'innerText');
|
||||
|
||||
expect(result).toEqual('address 26');
|
||||
});
|
||||
|
||||
it('should check the summary contains the order consignee', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.orderSummary.consignee, 'innerText');
|
||||
|
||||
expect(result).toEqual('Many places - Silla (Province one)');
|
||||
});
|
||||
|
||||
it('should check the summary contains the order subtotal', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.orderSummary.subtotal, 'innerText');
|
||||
|
||||
expect(result.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should check the summary contains the order vat', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.orderSummary.vat, 'innerText');
|
||||
|
||||
expect(result.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should check the summary contains the order total', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.orderSummary.total, 'innerText');
|
||||
|
||||
expect(result.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should check the summary contains the order sales', async() => {
|
||||
const result = await page.countElement(selectors.orderSummary.sale);
|
||||
|
||||
expect(result).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Travel basic data path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('buyer', 'travel');
|
||||
await page.accessToSearchResult('3');
|
||||
await page.accessToSection('travel.card.basicData');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should reach the thermograph section', async() => {
|
||||
const result = await page.expectURL('/basic-data');
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should set a wrong delivery date then receive an error on submit', async() => {
|
||||
await page.datePicker(selectors.travelBasicDada.deliveryDate, -1, null);
|
||||
await page.waitToClick(selectors.travelBasicDada.save);
|
||||
const result = await page.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Landing cannot be lesser than shipment');
|
||||
});
|
||||
|
||||
it('should undo the changes', async() => {
|
||||
await page.waitToClick(selectors.travelBasicDada.undoChanges);
|
||||
await page.waitToClick(selectors.travelBasicDada.save);
|
||||
const result = await page.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('No changes to save');
|
||||
});
|
||||
|
||||
it('should now edit the whole form then save', async() => {
|
||||
await page.clearInput(selectors.travelBasicDada.reference);
|
||||
await page.write(selectors.travelBasicDada.reference, 'new reference!');
|
||||
await page.waitFor(2000);
|
||||
await page.autocompleteSearch(selectors.travelBasicDada.agency, 'Entanglement');
|
||||
await page.autocompleteSearch(selectors.travelBasicDada.outputWarehouse, 'Warehouse Three');
|
||||
await page.autocompleteSearch(selectors.travelBasicDada.inputWarehouse, 'Warehouse Four');
|
||||
await page.waitToClick(selectors.travelBasicDada.delivered);
|
||||
await page.waitToClick(selectors.travelBasicDada.received);
|
||||
await page.waitToClick(selectors.travelBasicDada.save);
|
||||
const result = await page.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Data saved!');
|
||||
});
|
||||
|
||||
it('should reload the section and check the reference was saved', async() => {
|
||||
await page.reloadSection('travel.card.basicData');
|
||||
const result = await page.waitToGetProperty(selectors.travelBasicDada.reference, 'value');
|
||||
|
||||
expect(result).toEqual('new reference!');
|
||||
});
|
||||
|
||||
it('should check the agency was saved', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.travelBasicDada.agency, 'value');
|
||||
|
||||
expect(result).toEqual('Entanglement');
|
||||
});
|
||||
|
||||
it('should check the output warehouse date was saved', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.travelBasicDada.outputWarehouse, 'value');
|
||||
|
||||
expect(result).toEqual('Warehouse Three');
|
||||
});
|
||||
|
||||
it('should check the input warehouse date was saved', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.travelBasicDada.inputWarehouse, 'value');
|
||||
|
||||
expect(result).toEqual('Warehouse Four');
|
||||
});
|
||||
|
||||
it(`should check the delivered checkbox was saved even tho it doesn't make sense`, async() => {
|
||||
await page.waitForClassPresent(selectors.travelBasicDada.delivered, 'checked');
|
||||
});
|
||||
|
||||
it(`should check the received checkbox was saved even tho it doesn't make sense`, async() => {
|
||||
await page.waitForClassPresent(selectors.travelBasicDada.received, 'checked');
|
||||
});
|
||||
|
||||
it('should navigate to the travel logs', async() => {
|
||||
await page.accessToSection('travel.card.log');
|
||||
const result = await page.expectURL('/log');
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should check the 1st log contains details from the changes made', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText');
|
||||
|
||||
expect(result).toContain('new reference!');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Entry descriptor path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('buyer', 'entry');
|
||||
await page.accessToSearchResult('2');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should reach the second entry summary section', async() => {
|
||||
let url = await page.expectURL('#!/entry/2/summary');
|
||||
|
||||
expect(url).toBe(true);
|
||||
});
|
||||
|
||||
it('should show some entry information', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.entryDescriptor.agency, 'innerText');
|
||||
|
||||
expect(result).toContain('inhouse pickup');
|
||||
});
|
||||
|
||||
it('should click the travels button to be redirected to the travels index filtered by the current agency', async() => {
|
||||
await page.waitToClick(selectors.entryDescriptor.travelsQuicklink);
|
||||
const url = await page.expectURL('/travel/index');
|
||||
const filter = await page.expectURL('agencyFk');
|
||||
|
||||
expect(url).toBe(true);
|
||||
expect(filter).toBe(true);
|
||||
});
|
||||
|
||||
it('should go back to the entry summary', async() => {
|
||||
await page.waitToClick(selectors.globalItems.homeButton);
|
||||
await page.selectModule('entry');
|
||||
await page.accessToSearchResult('2');
|
||||
let url = await page.expectURL('#!/entry/2/summary');
|
||||
|
||||
expect(url).toBe(true);
|
||||
});
|
||||
|
||||
it('should click the entries button to be redirected to the entries index filtered by the current supplier', async() => {
|
||||
await page.waitToClick(selectors.entryDescriptor.entriesQuicklink);
|
||||
const url = await page.expectURL('/entry/index');
|
||||
const supplierFilter = await page.expectURL('supplierFk');
|
||||
const toFilter = await page.expectURL('to');
|
||||
const fromFilter = await page.expectURL('from');
|
||||
|
||||
expect(url).toBe(true);
|
||||
expect(supplierFilter).toBe(true);
|
||||
expect(toFilter).toBe(true);
|
||||
expect(fromFilter).toBe(true);
|
||||
});
|
||||
});
|
|
@ -30,7 +30,7 @@
|
|||
ng-click="$ctrl.onClear($event)">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
icon="cloud_upload"
|
||||
icon="attach_file"
|
||||
vn-tooltip="Select a file"
|
||||
ng-click="$ctrl.openFileSelector()">
|
||||
</vn-icon>
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('Client isValidClient', () => {
|
|||
});
|
||||
|
||||
it('should call the isValidClient() method with an unexistant id and receive false', async() => {
|
||||
let id = 999999;
|
||||
let id = 999;
|
||||
let result = await app.models.Client.isValidClient(id);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
|
|
|
@ -56,7 +56,16 @@
|
|||
label="File"
|
||||
ng-model="$ctrl.dms.files"
|
||||
on-change="$ctrl.onFileChange($files)"
|
||||
accept=".pdf, .png, .jpg, .jpeg, application/zip, application/rar, application/x-7z-compressed">
|
||||
accept="{{$ctrl.allowedContentTypes}}"
|
||||
required="true"
|
||||
multiple="true">
|
||||
<append>
|
||||
<vn-icon vn-none
|
||||
color-marginal
|
||||
title="{{$ctrl.contentTypesInfo}}"
|
||||
icon="info">
|
||||
</vn-icon>
|
||||
</append>
|
||||
</vn-input-file>
|
||||
</vn-horizontal>
|
||||
<vn-vertical>
|
||||
|
|
|
@ -53,11 +53,6 @@
|
|||
{{::document.dms.description}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-check disabled="true"
|
||||
ng-model="document.dms.hasFile">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
title="{{'Download file' | translate}}"
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('ticket-request confirm()', () => {
|
|||
expect(error.message).toEqual(`That item doesn't exists`);
|
||||
});
|
||||
|
||||
it(`should throw an error if the item is not available`, async() => {
|
||||
it('should throw an error if the item is not available', async() => {
|
||||
const requestId = 5;
|
||||
const itemId = 4;
|
||||
const quantity = 99999;
|
||||
|
|
|
@ -67,6 +67,10 @@ module.exports = Self => {
|
|||
arg: 'problems',
|
||||
type: 'Boolean',
|
||||
description: `Whether to show only tickets with problems`
|
||||
}, {
|
||||
arg: 'pending',
|
||||
type: 'Boolean',
|
||||
description: `Whether to show only tickets with state 'Pending'`
|
||||
}, {
|
||||
arg: 'mine',
|
||||
type: 'Boolean',
|
||||
|
@ -130,7 +134,7 @@ module.exports = Self => {
|
|||
dateTo.setHours(23, 59, 0, 0);
|
||||
}
|
||||
|
||||
let where = buildFilter(ctx.args, (param, value) => {
|
||||
const where = buildFilter(ctx.args, (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return /^\d+$/.test(value)
|
||||
|
@ -155,6 +159,17 @@ module.exports = Self => {
|
|||
return {'c.salesPersonFk': {inq: teamIds}};
|
||||
case 'alertLevel':
|
||||
return {'ts.alertLevel': value};
|
||||
case 'pending':
|
||||
if (value) {
|
||||
return {and: [
|
||||
{'st.alertLevel': 0},
|
||||
{'st.code': {neq: 'OK'}}
|
||||
]};
|
||||
} else {
|
||||
return {and: [
|
||||
{'st.alertLevel': {gt: 0}}
|
||||
]};
|
||||
}
|
||||
case 'id':
|
||||
case 'clientFk':
|
||||
case 'agencyModeFk':
|
||||
|
@ -244,7 +259,6 @@ module.exports = Self => {
|
|||
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id
|
||||
LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`);
|
||||
|
||||
|
||||
let condition;
|
||||
let hasProblem;
|
||||
let range;
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
|||
|
||||
let alertLevel = state ? state.alertLevel : null;
|
||||
let ticket = await Self.app.models.Ticket.findById(id, {
|
||||
fields: ['isDeleted', 'clientFk', 'refFk'],
|
||||
fields: ['clientFk'],
|
||||
include: [{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
|
@ -42,13 +42,13 @@ module.exports = Self => {
|
|||
}
|
||||
}]
|
||||
});
|
||||
const isLocked = await Self.app.models.Ticket.isLocked(id);
|
||||
|
||||
const isDeleted = ticket && ticket.isDeleted;
|
||||
const isOnDelivery = (alertLevel && alertLevel > 0);
|
||||
const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0);
|
||||
const isNormalClient = ticket && ticket.client().type().code == 'normal';
|
||||
const isInvoiced = ticket && ticket.refFk;
|
||||
const validAlertAndRoleNormalClient = (alertLevelGreaterThanZero && isNormalClient && !isValidRole);
|
||||
|
||||
if (!ticket || isInvoiced || isDeleted || (isOnDelivery && isNormalClient && !isValidRole))
|
||||
if (!ticket || validAlertAndRoleNormalClient || isLocked)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('isLocked', {
|
||||
description: 'Check if a ticket is invoiced or deleted',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'the ticket id',
|
||||
http: {source: 'path'}
|
||||
}],
|
||||
returns: {
|
||||
type: 'boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/isLocked`,
|
||||
verb: 'get'
|
||||
}
|
||||
});
|
||||
|
||||
Self.isLocked = async id => {
|
||||
const ticket = await Self.app.models.Ticket.findById(id, {
|
||||
fields: ['isDeleted', 'refFk']
|
||||
});
|
||||
|
||||
const isDeleted = ticket && ticket.isDeleted;
|
||||
const isInvoiced = ticket && ticket.refFk;
|
||||
|
||||
if (!ticket || isInvoiced || isDeleted)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
|
@ -41,6 +41,34 @@ describe('ticket filter()', () => {
|
|||
const firstRow = result[0];
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
expect(firstRow.ticketFk).toEqual(11);
|
||||
expect(firstRow.id).toEqual(11);
|
||||
});
|
||||
|
||||
it('should return the tickets with grouped state "Pending" and not "Ok"', async() => {
|
||||
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: true}};
|
||||
const filter = {};
|
||||
const result = await app.models.Ticket.filter(ctx, filter);
|
||||
const firstRow = result[0];
|
||||
const secondRow = result[1];
|
||||
const thirdRow = result[2];
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
expect(firstRow.state).toEqual('Arreglar');
|
||||
expect(secondRow.state).toEqual('Arreglar');
|
||||
expect(thirdRow.state).toEqual('Arreglar');
|
||||
});
|
||||
|
||||
it('should return the tickets that are not pending', async() => {
|
||||
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}};
|
||||
const filter = {};
|
||||
const result = await app.models.Ticket.filter(ctx, filter);
|
||||
const firstRow = result[0];
|
||||
const secondRow = result[1];
|
||||
const thirdRow = result[2];
|
||||
|
||||
expect(result.length).toEqual(13);
|
||||
expect(firstRow.state).toEqual('Entregado');
|
||||
expect(secondRow.state).toEqual('Entregado');
|
||||
expect(thirdRow.state).toEqual('Entregado');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket isEditable()', () => {
|
||||
it('should return false if the given ticket is not editable', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
let result = await app.models.Ticket.isEditable(ctx, 2);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false if the given ticket does not exist', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
let result = await app.models.Ticket.isEditable(ctx, 99999);
|
||||
|
@ -15,37 +8,46 @@ describe('ticket isEditable()', () => {
|
|||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false if the given ticket isDeleted', async() => {
|
||||
it(`should return false if the given ticket isn't invoiced but isDeleted`, async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
let result = await app.models.Ticket.isEditable(ctx, 19);
|
||||
let deletedTicket = await app.models.Ticket.findOne({
|
||||
where: {
|
||||
invoiceOut: null,
|
||||
isDeleted: true
|
||||
},
|
||||
fields: ['id']
|
||||
});
|
||||
|
||||
let result = await app.models.Ticket.isEditable(ctx, deletedTicket.id);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if the given ticket is editable', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
|
||||
let result = await app.models.Ticket.isEditable(ctx, 16);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should be able to edit a deleted or invoiced ticket if the role is salesAssistant', async() => {
|
||||
it('should not be able to edit a deleted or invoiced ticket even for salesAssistant', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 21}}};
|
||||
let result = await app.models.Ticket.isEditable(ctx, 8);
|
||||
let result = await app.models.Ticket.isEditable(ctx, 19);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be able to edit a deleted or invoiced ticket if the role is productionBoss', async() => {
|
||||
it('should not be able to edit a deleted or invoiced ticket even for productionBoss', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 50}}};
|
||||
let result = await app.models.Ticket.isEditable(ctx, 8);
|
||||
let result = await app.models.Ticket.isEditable(ctx, 19);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not be able to edit a deleted or invoiced ticket if the role is salesPerson', async() => {
|
||||
it('should not be able to edit a deleted or invoiced ticket even for salesPerson', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 18}}};
|
||||
let result = await app.models.Ticket.isEditable(ctx, 8);
|
||||
let result = await app.models.Ticket.isEditable(ctx, 19);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket isLocked()', () => {
|
||||
it('should return true if the given ticket does not exist', async() => {
|
||||
let result = await app.models.Ticket.isLocked(99999);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true if the given ticket is invoiced', async() => {
|
||||
let invoicedTicket = await app.models.Ticket.findOne({
|
||||
where: {invoiceOut: {neq: null}},
|
||||
fields: ['id']
|
||||
});
|
||||
|
||||
let result = await app.models.Ticket.isLocked(invoicedTicket.id);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it(`should return true if the given ticket isn't invoiced but deleted`, async() => {
|
||||
let deletedTicket = await app.models.Ticket.findOne({
|
||||
where: {
|
||||
invoiceOut: null,
|
||||
isDeleted: true
|
||||
},
|
||||
fields: ['id']
|
||||
});
|
||||
|
||||
let result = await app.models.Ticket.isLocked(deletedTicket.id);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -35,6 +35,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const tx = await Self.beginTransaction({});
|
||||
|
||||
|
@ -68,8 +69,14 @@ module.exports = Self => {
|
|||
if (!allFromSameTicket)
|
||||
throw new UserError('All sales must belong to the same ticket');
|
||||
|
||||
const isEditable = await models.Ticket.isEditable(ctx, id);
|
||||
if (!isEditable)
|
||||
const isLocked = await models.Ticket.isLocked(id);
|
||||
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
|
||||
const state = await Self.app.models.TicketState.findOne({
|
||||
where: {ticketFk: id}
|
||||
});
|
||||
const alertLevel = state ? state.alertLevel : null;
|
||||
|
||||
if (isLocked || (!isSalesPerson && alertLevel > 0 ))
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
const ticket = await models.Ticket.findById(id, {
|
||||
|
|
|
@ -29,6 +29,7 @@ module.exports = Self => {
|
|||
require('../methods/ticket/recalculateComponents')(Self);
|
||||
require('../methods/ticket/deleteStowaway')(Self);
|
||||
require('../methods/ticket/sendSms')(Self);
|
||||
require('../methods/ticket/isLocked')(Self);
|
||||
|
||||
Self.observe('before save', async function(ctx) {
|
||||
if (ctx.isNewInstance) return;
|
||||
|
|
|
@ -51,11 +51,6 @@
|
|||
{{::document.dms.description}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-check disabled="true"
|
||||
field="document.dms.hasFile">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
title="{{'Download file' | translate}}"
|
||||
|
|
|
@ -165,8 +165,8 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
<span ng-class="{'link': $ctrl.isEditable}"
|
||||
title="{{$ctrl.isEditable ? 'Edit discount' : ''}}"
|
||||
<span ng-class="{'link': !$ctrl.isLocked}"
|
||||
title="{{!$ctrl.isLocked ? 'Edit discount' : ''}}"
|
||||
ng-click="$ctrl.showEditDiscountPopover($event, sale)">
|
||||
{{(sale.discount / 100) | percentage}}
|
||||
</span>
|
||||
|
|
|
@ -46,6 +46,7 @@ class Controller {
|
|||
set ticket(value) {
|
||||
this._ticket = value;
|
||||
this.isTicketEditable();
|
||||
this.isTicketLocked();
|
||||
}
|
||||
|
||||
get sales() {
|
||||
|
@ -354,7 +355,7 @@ class Controller {
|
|||
}
|
||||
|
||||
showEditDiscountPopover(event, sale) {
|
||||
if (!this.isEditable) return;
|
||||
if (this.isLocked) return;
|
||||
|
||||
this.sale = sale;
|
||||
this.edit = [{
|
||||
|
@ -540,6 +541,12 @@ class Controller {
|
|||
});
|
||||
}
|
||||
|
||||
isTicketLocked() {
|
||||
this.$http.get(`Tickets/${this.$state.params.id}/isLocked`).then(res => {
|
||||
this.isLocked = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
hasOneSaleSelected() {
|
||||
if (this.totalCheckedLines() === 1)
|
||||
return true;
|
||||
|
|
|
@ -69,6 +69,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
$httpBackend.when('POST', `Claims/createFromSales`, {claim: claim, sales: sales}).respond(claim);
|
||||
$httpBackend.expect('POST', `Claims/createFromSales`).respond(claim);
|
||||
controller.createClaim();
|
||||
|
@ -98,6 +99,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
let result = controller.checkedLines();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -116,6 +118,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.expectGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.expectGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.onStateOkClick();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -129,6 +132,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.onStateChange(3);
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
@ -142,6 +146,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.onRemoveLinesClick('accept');
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -183,6 +188,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.unmarkAsReserved(false);
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
@ -213,6 +219,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.updateQuantity(sale);
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -232,6 +239,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.updateConcept(sale);
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -262,6 +270,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.addSale(newSale);
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -287,6 +296,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.transferSales(13);
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -305,6 +315,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.setTransferParams();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -330,6 +341,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
controller.newOrderFromTicket();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
@ -353,6 +365,7 @@ describe('Ticket', () => {
|
|||
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
|
||||
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
|
||||
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
|
||||
$httpBackend.whenGET(`Tickets/1/isLocked`).respond();
|
||||
|
||||
controller.calculateSalePrice();
|
||||
$httpBackend.flush();
|
||||
|
|
|
@ -113,10 +113,16 @@
|
|||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Problems"
|
||||
label="With problems"
|
||||
ng-model="filter.problems"
|
||||
triple-state="true">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Pending"
|
||||
ng-model="filter.pending"
|
||||
triple-state="true">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-mt-lg">
|
||||
<vn-submit label="Search"></vn-submit>
|
||||
|
|
|
@ -11,3 +11,5 @@ My team: Mi equipo
|
|||
Order id: Id pedido
|
||||
Grouped States: Estado agrupado
|
||||
Days onward: Días adelante
|
||||
With problems: Con problemas
|
||||
Pending: Pendientes
|
|
@ -14,6 +14,10 @@ module.exports = Self => {
|
|||
type: 'String',
|
||||
description: 'The thermograph id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'state',
|
||||
type: 'String',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
|
@ -48,13 +52,12 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.createThermograph = async(ctx, id, thermographId) => {
|
||||
Self.createThermograph = async(ctx, id, thermographId, state) => {
|
||||
const models = Self.app.models;
|
||||
const tx = await Self.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const travelThermograph = await models.TravelThermograph.findOne({
|
||||
where: {
|
||||
thermographFk: thermographId,
|
||||
|
@ -70,7 +73,8 @@ module.exports = Self => {
|
|||
|
||||
await travelThermograph.updateAttributes({
|
||||
dmsFk: firstDms.id,
|
||||
travelFk: id
|
||||
travelFk: id,
|
||||
result: state
|
||||
}, options);
|
||||
|
||||
await tx.commit();
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('updateThermograph', {
|
||||
description: 'updates a file properties or file',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The travel id',
|
||||
http: {source: 'path'}
|
||||
}, {
|
||||
arg: 'thermographId',
|
||||
type: 'String',
|
||||
description: 'The thermograph id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'state',
|
||||
type: 'String',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: 'The warehouse id'
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: 'The company id'
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: 'The dms type id'
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String'
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String'
|
||||
}, {
|
||||
arg: 'hasFileAttached',
|
||||
type: 'Boolean',
|
||||
description: 'True if has an attached file'
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/updateThermograph`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.updateThermograph = async(ctx, id, thermographId, state) => {
|
||||
const models = Self.app.models;
|
||||
const tx = await Self.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const travelThermograph = await models.TravelThermograph.findOne({
|
||||
where: {
|
||||
thermographFk: thermographId,
|
||||
travelFk: id
|
||||
}
|
||||
}, options);
|
||||
|
||||
if (!travelThermograph)
|
||||
throw new UserError('No valid travel thermograph found');
|
||||
|
||||
const dmsFk = travelThermograph.dmsFk;
|
||||
await models.Dms.updateFile(ctx, dmsFk, options);
|
||||
await travelThermograph.updateAttributes({
|
||||
result: state
|
||||
}, options);
|
||||
|
||||
await tx.commit();
|
||||
return travelThermograph;
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -4,4 +4,5 @@ module.exports = Self => {
|
|||
require('../methods/travel/filter')(Self);
|
||||
require('../methods/travel/createThermograph')(Self);
|
||||
require('../methods/travel/deleteThermograph')(Self);
|
||||
require('../methods/travel/updateThermograph')(Self);
|
||||
};
|
||||
|
|
|
@ -11,4 +11,5 @@ import './log';
|
|||
import './create';
|
||||
import './thermograph/index/';
|
||||
import './thermograph/create/';
|
||||
import './thermograph/edit/';
|
||||
import './descriptor-popover';
|
||||
|
|
|
@ -81,6 +81,15 @@
|
|||
"travel": "$ctrl.travel"
|
||||
},
|
||||
"acl": ["buyer"]
|
||||
}, {
|
||||
"url" : "/:thermographId/edit",
|
||||
"state": "travel.card.thermograph.edit",
|
||||
"component": "vn-travel-thermograph-edit",
|
||||
"description": "Edit thermograph",
|
||||
"params": {
|
||||
"travel": "$ctrl.travel"
|
||||
},
|
||||
"acl": ["buyer"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -9,6 +9,35 @@
|
|||
enctype="multipart/form-data">
|
||||
<div class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
label="Thermograph"
|
||||
ng-model="$ctrl.dms.thermographId"
|
||||
url="TravelThermographs"
|
||||
where="{travelFk: null}"
|
||||
show-field="thermographFk"
|
||||
value-field="thermographFk">
|
||||
</vn-autocomplete>
|
||||
<vn-textfield vn-one
|
||||
label="State"
|
||||
ng-model="$ctrl.dms.state"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield vn-one
|
||||
label="Reference"
|
||||
ng-model="$ctrl.dms.reference"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
<vn-autocomplete vn-one
|
||||
label="Type"
|
||||
ng-model="$ctrl.dms.dmsTypeId"
|
||||
url="DmsTypes"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
label="Company"
|
||||
|
@ -25,28 +54,6 @@
|
|||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
label="Type"
|
||||
ng-model="$ctrl.dms.dmsTypeId"
|
||||
url="DmsTypes"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
<vn-textfield vn-one
|
||||
label="Reference"
|
||||
ng-model="$ctrl.dms.reference"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
<vn-autocomplete vn-one
|
||||
label="Thermograph"
|
||||
ng-model="$ctrl.dms.thermographId"
|
||||
url="TravelThermographs"
|
||||
where="{travelFk: null}"
|
||||
show-field="thermographFk"
|
||||
value-field="thermographFk">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textarea vn-one vn-focus
|
||||
label="Description"
|
||||
|
|
|
@ -8,7 +8,7 @@ class Controller {
|
|||
this.$translate = $translate;
|
||||
this.vnApp = vnApp;
|
||||
this.vnConfig = vnConfig;
|
||||
this.dms = {files: []};
|
||||
this.dms = {files: [], state: 'Ok'};
|
||||
}
|
||||
|
||||
get travel() {
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.dms">
|
||||
</vn-watcher>
|
||||
<form
|
||||
name="form"
|
||||
ng-submit="$ctrl.onSubmit()"
|
||||
class="vn-ma-md"
|
||||
enctype="multipart/form-data">
|
||||
<div class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
label="Thermograph"
|
||||
ng-model="$ctrl.thermograph.thermographId"
|
||||
url="TravelThermographs"
|
||||
show-field="thermographFk"
|
||||
value-field="thermographFk"
|
||||
disabled="true">
|
||||
</vn-autocomplete>
|
||||
<vn-textfield vn-one
|
||||
label="State"
|
||||
ng-model="$ctrl.thermograph.state"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield vn-one
|
||||
label="Reference"
|
||||
ng-model="$ctrl.thermograph.reference"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
<vn-autocomplete vn-one
|
||||
label="Type"
|
||||
ng-model="$ctrl.thermograph.dmsTypeId"
|
||||
url="DmsTypes"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
label="Company"
|
||||
ng-model="$ctrl.thermograph.companyId"
|
||||
url="Companies"
|
||||
show-field="code"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-one
|
||||
label="Warehouse"
|
||||
ng-model="$ctrl.thermograph.warehouseId"
|
||||
url="Warehouses"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textarea vn-one vn-focus
|
||||
label="Description"
|
||||
ng-model="$ctrl.thermograph.description"
|
||||
rule>
|
||||
</vn-textarea>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-input-file
|
||||
vn-one
|
||||
label="File"
|
||||
ng-model="$ctrl.thermograph.files"
|
||||
on-change="$ctrl.onFileChange($files)"
|
||||
accept="{{$ctrl.allowedContentTypes}}"
|
||||
multiple="true">
|
||||
<append>
|
||||
<vn-icon vn-none
|
||||
color-marginal
|
||||
title="{{$ctrl.contentTypesInfo}}"
|
||||
icon="info">
|
||||
</vn-icon>
|
||||
</append>
|
||||
</vn-input-file>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit label="Save"></vn-submit>
|
||||
<vn-button ui-sref="travel.card.thermograph.index" label="Cancel"></vn-button>
|
||||
</vn-button-bar>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,98 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from 'core/lib/component';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends Component {
|
||||
get travel() {
|
||||
return this._travel;
|
||||
}
|
||||
|
||||
set travel(value) {
|
||||
this._travel = value;
|
||||
|
||||
if (value) {
|
||||
this.setDefaultParams();
|
||||
this.getAllowedContentTypes();
|
||||
}
|
||||
}
|
||||
|
||||
getAllowedContentTypes() {
|
||||
this.$http.get('TravelThermographs/allowedContentTypes').then(res => {
|
||||
const contentTypes = res.data.join(', ');
|
||||
this.allowedContentTypes = contentTypes;
|
||||
});
|
||||
}
|
||||
|
||||
get contentTypesInfo() {
|
||||
return this.$translate.instant('ContentTypesInfo', {
|
||||
allowedContentTypes: this.allowedContentTypes
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultParams() {
|
||||
const filterObj = {include: {relation: 'dms'}};
|
||||
const filter = encodeURIComponent(JSON.stringify(filterObj));
|
||||
const path = `TravelThermographs/${this.$params.thermographId}?filter=${filter}`;
|
||||
this.$http.get(path).then(res => {
|
||||
const thermograph = res.data && res.data;
|
||||
this.thermograph = {
|
||||
thermographId: thermograph.thermographFk,
|
||||
state: thermograph.result,
|
||||
reference: thermograph.dms.reference,
|
||||
warehouseId: thermograph.dms.warehouseFk,
|
||||
companyId: thermograph.dms.companyFk,
|
||||
dmsTypeId: thermograph.dms.dmsTypeFk,
|
||||
description: thermograph.dms.description,
|
||||
hasFile: thermograph.dms.hasFile,
|
||||
hasFileAttached: false,
|
||||
files: []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const query = `travels/${this.$params.id}/updateThermograph`;
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: query,
|
||||
params: this.thermograph,
|
||||
headers: {
|
||||
'Content-Type': undefined
|
||||
},
|
||||
transformRequest: files => {
|
||||
const formData = new FormData();
|
||||
|
||||
for (let i = 0; i < files.length; i++)
|
||||
formData.append(files[i].name, files[i]);
|
||||
|
||||
return formData;
|
||||
},
|
||||
data: this.thermograph.files
|
||||
};
|
||||
this.$http(options).then(res => {
|
||||
if (res) {
|
||||
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
|
||||
this.$.watcher.updateOriginalData();
|
||||
this.$state.go('travel.card.thermograph.index');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onFileChange(files) {
|
||||
let hasFileAttached = false;
|
||||
if (files.length > 0)
|
||||
hasFileAttached = true;
|
||||
|
||||
this.$.$applyAsync(() => {
|
||||
this.thermograph.hasFileAttached = hasFileAttached;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnTravelThermographEdit', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
travel: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,122 @@
|
|||
import './index';
|
||||
import watcher from 'core/mocks/watcher.js';
|
||||
|
||||
describe('Worker', () => {
|
||||
describe('Component vnTravelThermographEdit', () => {
|
||||
let controller;
|
||||
let $scope;
|
||||
let $element;
|
||||
let $httpBackend;
|
||||
let $httpParamSerializer;
|
||||
|
||||
beforeEach(ngModule('travel'));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
|
||||
$scope = $rootScope.$new();
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpParamSerializer = _$httpParamSerializer_;
|
||||
$element = angular.element(`<vn-travel-thermograph-edit></vn-travel-thermograph-edit`);
|
||||
controller = $componentController('vnTravelThermographEdit', {$element, $scope});
|
||||
controller._travel = {id: 3};
|
||||
controller.$params = {id: 3, thermographId: 6};
|
||||
controller.$.watcher = watcher;
|
||||
}));
|
||||
|
||||
describe('travel() setter', () => {
|
||||
it('should set the travel data and then call setDefaultParams() and getAllowedContentTypes()', () => {
|
||||
jest.spyOn(controller, 'setDefaultParams');
|
||||
jest.spyOn(controller, 'getAllowedContentTypes');
|
||||
controller._travel = undefined;
|
||||
controller.travel = {
|
||||
id: 3
|
||||
};
|
||||
|
||||
expect(controller.setDefaultParams).toHaveBeenCalledWith();
|
||||
expect(controller.travel).toBeDefined();
|
||||
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultParams()', () => {
|
||||
it('should perform a GET query and define the dms property on controller', () => {
|
||||
const thermographId = 6;
|
||||
const expectedResponse = {
|
||||
thermographFk: 6,
|
||||
result: 'Ok',
|
||||
dms: {
|
||||
reference: '123456-01',
|
||||
warehouseFk: 1,
|
||||
companyFk: 442,
|
||||
dmsTypeFk: 3,
|
||||
description: 'Test'
|
||||
}
|
||||
};
|
||||
|
||||
const filterObj = {include: {relation: 'dms'}};
|
||||
const filter = encodeURIComponent(JSON.stringify(filterObj));
|
||||
const query = `TravelThermographs/${thermographId}?filter=${filter}`;
|
||||
$httpBackend.expect('GET', query).respond(expectedResponse);
|
||||
controller.setDefaultParams();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.thermograph).toBeDefined();
|
||||
expect(controller.thermograph.reference).toEqual('123456-01');
|
||||
expect(controller.thermograph.dmsTypeId).toEqual(3);
|
||||
expect(controller.thermograph.state).toEqual('Ok');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onFileChange()', () => {
|
||||
it('should set dms hasFileAttached property to true if has any files', () => {
|
||||
const files = [{id: 1, name: 'MyFile'}];
|
||||
controller.thermograph = {hasFileAttached: false};
|
||||
controller.onFileChange(files);
|
||||
$scope.$apply();
|
||||
|
||||
expect(controller.thermograph.hasFileAttached).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllowedContentTypes()', () => {
|
||||
it('should make an HTTP GET request to get the allowed content types', () => {
|
||||
const expectedResponse = ['image/png', 'image/jpg'];
|
||||
$httpBackend.when('GET', `TravelThermographs/allowedContentTypes`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `TravelThermographs/allowedContentTypes`);
|
||||
controller.getAllowedContentTypes();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.allowedContentTypes).toBeDefined();
|
||||
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('contentTypesInfo()', () => {
|
||||
it('should return a description with a list of allowed content types', () => {
|
||||
controller.allowedContentTypes = ['image/png', 'image/jpg'];
|
||||
const expectedTypes = controller.allowedContentTypes.join(', ');
|
||||
|
||||
const expectedResult = `Allowed content types: ${expectedTypes}`;
|
||||
jest.spyOn(controller.$translate, 'instant').mockReturnValue(expectedResult);
|
||||
|
||||
const result = controller.contentTypesInfo;
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSubmit()', () => {
|
||||
it('should make an HTTP POST request to save the form data', () => {
|
||||
jest.spyOn(controller.$.watcher, 'updateOriginalData');
|
||||
|
||||
const files = [{id: 1, name: 'MyFile'}];
|
||||
controller.thermograph = {files};
|
||||
const serializedParams = $httpParamSerializer(controller.thermograph);
|
||||
const query = `travels/${controller.$params.id}/updateThermograph?${serializedParams}`;
|
||||
|
||||
$httpBackend.expect('POST', query).respond({});
|
||||
controller.onSubmit();
|
||||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
vn-ticket-request {
|
||||
.vn-textfield {
|
||||
margin: 0!important;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,11 +23,11 @@
|
|||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="thermograph in $ctrl.travelThermographs">
|
||||
<vn-td>{{thermograph.thermographFk}} </vn-td>
|
||||
<vn-td>{{thermograph.temperature}} </vn-td>
|
||||
<vn-td expand>{{thermograph.result}}</vn-td>
|
||||
<vn-td>{{thermograph.warehouse.name}}</vn-td>
|
||||
<vn-td>{{thermograph.created | date: 'dd/MM/yyyy'}}</vn-td>
|
||||
<vn-td>{{::thermograph.thermographFk}} </vn-td>
|
||||
<vn-td>{{::thermograph.temperature}} </vn-td>
|
||||
<vn-td expand>{{::thermograph.result}}</vn-td>
|
||||
<vn-td>{{::thermograph.warehouse.name}}</vn-td>
|
||||
<vn-td>{{::thermograph.created | date: 'dd/MM/yyyy'}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
href="api/dms/{{::thermograph.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
|
||||
|
@ -37,6 +37,12 @@
|
|||
</vn-icon-button>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button ui-sref="travel.card.thermograph.edit({thermographId: {{::thermograph.id}}})"
|
||||
icon="edit"
|
||||
title="{{'Edit file' | translate}}">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button
|
||||
icon="delete"
|
||||
|
|
|
@ -12,6 +12,7 @@ FileDescription: Travel id {{travelId}}
|
|||
ContentTypesInfo: 'Tipos de archivo permitidos: {{allowedContentTypes}}'
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
Add thermograph: Añadir termógrafo
|
||||
Edit thermograph: Editar termógrafo
|
||||
Thermograph deleted: Termógrafo eliminado
|
||||
Thermograph: Termógrafo
|
||||
Are you sure you want to remove the thermograph?: ¿Seguro que quieres quitar el termógrafo?
|
|
@ -5,7 +5,7 @@ module.exports = Self => {
|
|||
accepts: {
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The document id',
|
||||
description: 'The worker document id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
returns: {
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number"
|
||||
"type": "Number",
|
||||
"id": true
|
||||
},
|
||||
"dmsFk": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"required": true,
|
||||
"mysql": {
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
<vn-th field="reference" shrink>Reference</vn-th>
|
||||
<vn-th expand>Description</vn-th>
|
||||
<vn-th field="hasFile" shrink>Original</vn-th>
|
||||
<vn-th shrink>File</vn-th>
|
||||
<vn-th field="created">Created</vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
|
@ -39,17 +38,13 @@
|
|||
{{::document.dms.description}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-check disabled="true"
|
||||
ng-model="document.dms.hasFile">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<a target="_blank"
|
||||
title="{{'Download file' | translate}}"
|
||||
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">{{::document.dms.file}}
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
|
|
|
@ -58,8 +58,8 @@ class Controller extends Component {
|
|||
|
||||
deleteDms(response) {
|
||||
if (response === 'accept') {
|
||||
const dmsFk = this.workerDms[this.dmsIndex].dmsFk;
|
||||
const query = `WorkerDms/${dmsFk}/removeFile`;
|
||||
const workerDmsId = this.workerDms[this.dmsIndex].id;
|
||||
const query = `WorkerDms/${workerDmsId}/removeFile`;
|
||||
this.$http.post(query).then(() => {
|
||||
this.$.model.remove(this.dmsIndex);
|
||||
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
|
||||
|
|
|
@ -22,15 +22,15 @@ describe('Worker', () => {
|
|||
|
||||
describe('deleteDms()', () => {
|
||||
it('should make an HTTP Post query', () => {
|
||||
const dmsId = 4;
|
||||
const workerDmsId = 1;
|
||||
const dmsIndex = 0;
|
||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||
jest.spyOn(controller.$.model, 'remove');
|
||||
controller.workerDms = [{dmsFk: 4}];
|
||||
controller.workerDms = [{id: 1, dmsFk: 4}];
|
||||
controller.dmsIndex = dmsIndex;
|
||||
|
||||
$httpBackend.when('POST', `WorkerDms/${dmsId}/removeFile`).respond({});
|
||||
$httpBackend.expect('POST', `WorkerDms/${dmsId}/removeFile`);
|
||||
$httpBackend.when('POST', `WorkerDms/${workerDmsId}/removeFile`).respond({});
|
||||
$httpBackend.expect('POST', `WorkerDms/${workerDmsId}/removeFile`);
|
||||
controller.deleteDms('accept');
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -157,6 +157,22 @@ table {
|
|||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent page break fix
|
||||
*/
|
||||
tbody {
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
display: block;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
thead, tbody tr {
|
||||
table-layout: fixed;
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row-oriented, .column-oriented {
|
||||
text-align: left;
|
||||
width: 100%
|
||||
|
@ -181,6 +197,10 @@ table {
|
|||
background-color: #e5e5e5
|
||||
}
|
||||
|
||||
.column-oriented tbody {
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
|
||||
.column-oriented tfoot {
|
||||
border-top: 2px solid #808080;
|
||||
}
|
||||
|
@ -190,7 +210,6 @@ table {
|
|||
}
|
||||
|
||||
.column-oriented .description {
|
||||
border-bottom: 1px solid #DDD;
|
||||
font-size: 0.8em
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
font-weight: bold
|
||||
}
|
||||
|
||||
.non-page-break {
|
||||
.no-page-break {
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid
|
||||
}
|
|
@ -59,37 +59,35 @@
|
|||
<tr>
|
||||
<th>{{$t('Code')}}</th>
|
||||
<th class="number">{{$t('Quantity')}}</th>
|
||||
<th>{{$t('Concept')}}</th>
|
||||
<th width="50%">{{$t('Concept')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="sale in sales">
|
||||
<tr class="font bold">
|
||||
<td>{{sale.itemFk}}</td>
|
||||
<td class="number">{{Math.trunc(sale.subtotal)}}</td>
|
||||
<td>{{sale.concept}}</td>
|
||||
</tr>
|
||||
<tr class="description">
|
||||
<td class="centered">
|
||||
<div v-if="sale.value5">
|
||||
<strong class="font gray">{{sale.tag5}}</strong>
|
||||
<span>{{sale.value5}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="centered">
|
||||
<div v-if="sale.value6">
|
||||
<strong class="font gray">{{sale.tag6}}</strong>
|
||||
<span>{{sale.value6}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="centered">
|
||||
<div v-if="sale.value7">
|
||||
<strong class="font gray">{{sale.tag7}}</strong>
|
||||
<span>{{sale.value7}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tbody v-for="sale in sales">
|
||||
<tr class="font bold">
|
||||
<td>{{sale.itemFk}}</td>
|
||||
<td class="number">{{Math.trunc(sale.subtotal)}}</td>
|
||||
<td width="50%">{{sale.concept}}</td>
|
||||
</tr>
|
||||
<tr class="description">
|
||||
<td>
|
||||
<div v-if="sale.value5">
|
||||
<strong class="font gray">{{sale.tag5}}</strong>
|
||||
<span>{{sale.value5}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="centered">
|
||||
<div v-if="sale.value6">
|
||||
<strong class="font gray">{{sale.tag6}}</strong>
|
||||
<span>{{sale.value6}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-right">
|
||||
<div v-if="sale.value7">
|
||||
<strong class="font gray">{{sale.tag7}}</strong>
|
||||
<span>{{sale.value7}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -64,8 +64,8 @@
|
|||
<th>{{$t('concept')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="sale in sales" v-bind:key="sale.id">
|
||||
<tbody v-for="sale in sales" v-bind:key="sale.id">
|
||||
<tr>
|
||||
<td class="font gray">{{sale.id}}</td>
|
||||
<td class="number">{{sale.quantity}}</td>
|
||||
<td class="number">{{sale.claimQuantity}}</td>
|
||||
|
|
|
@ -76,53 +76,48 @@
|
|||
<tr>
|
||||
<td>{{$t('reference')}}</td>
|
||||
<td class="number">{{$t('quantity')}}</td>
|
||||
<td>{{$t('concept')}}</td>
|
||||
<td width="50%">{{$t('concept')}}</td>
|
||||
<td class="number">{{$t('price')}}</td>
|
||||
<td class="centered">{{$t('discount')}}</td>
|
||||
<td class="centered">{{$t('vat')}}</td>
|
||||
<td class="number">{{$t('amount')}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="sale in sales">
|
||||
<tr class="font bold">
|
||||
<td>{{sale.itemFk}}</td>
|
||||
<td class="number">{{sale.quantity}}</td>
|
||||
<td>{{sale.concept}}</td>
|
||||
<td class="number">{{sale.price | currency('EUR', locale)}}</td>
|
||||
<td class="centered">{{sale.discount | percentage}}</td>
|
||||
<td class="centered">{{sale.vatType}}</td>
|
||||
<td class="number">{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
<tr class="description">
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="sale.value5">
|
||||
<strong class="font gray">{{sale.tag5}}</strong>
|
||||
<span>{{sale.value5}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3" class="centered">
|
||||
<div v-if="sale.value6">
|
||||
<strong class="font gray">{{sale.tag6}}</strong>
|
||||
<span>{{sale.value6}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="sale.value7">
|
||||
<strong class="font gray">{{sale.tag7}}</strong>
|
||||
<span>{{sale.value7}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="description phytosanitary" v-if="sale.passportNumber">
|
||||
<td colspan="7">
|
||||
{{sale.ediBotanic}} {{sale.denomination}} {{sale.countryCode}}-{{sale.passportNumber}}
|
||||
<span v-if="sale.isProtectedZone">ZP</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr >
|
||||
<td colspan="7"></td>
|
||||
<tbody v-for="sale in sales">
|
||||
<tr class="font bold">
|
||||
<td>{{sale.itemFk}}</td>
|
||||
<td class="number">{{sale.quantity}}</td>
|
||||
<td width="50%">{{sale.concept}}</td>
|
||||
<td class="number">{{sale.price | currency('EUR', locale)}}</td>
|
||||
<td class="centered">{{(sale.discount / 100) | percentage}}</td>
|
||||
<td class="centered">{{sale.vatType}}</td>
|
||||
<td class="number">{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
<tr class="description">
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="sale.value5">
|
||||
<strong class="font gray">{{sale.tag5}}</strong>
|
||||
<span>{{sale.value5}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3" class="centered">
|
||||
<div v-if="sale.value6">
|
||||
<strong class="font gray">{{sale.tag6}}</strong>
|
||||
<span>{{sale.value6}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="sale.value7">
|
||||
<strong class="font gray">{{sale.tag7}}</strong>
|
||||
<span>{{sale.value7}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="description phytosanitary" v-if="sale.passportNumber">
|
||||
<td colspan="7">
|
||||
{{sale.ediBotanic}} {{sale.denomination}} {{sale.countryCode}}-{{sale.passportNumber}}
|
||||
<span v-if="sale.isProtectedZone">ZP</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
|
@ -138,7 +133,7 @@
|
|||
|
||||
<div class="columns">
|
||||
<!-- Services block-->
|
||||
<div class="size100" v-if="services.length > 0">
|
||||
<div class="size100 no-page-break" v-if="services.length > 0">
|
||||
<h3>{{$t('services')}}</h3>
|
||||
<table class="column-oriented">
|
||||
<thead>
|
||||
|
@ -168,29 +163,35 @@
|
|||
<!-- End of services block -->
|
||||
|
||||
<!-- Taxes block -->
|
||||
<div id="taxes" class="size50 pull-right" v-if="taxes">
|
||||
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
|
||||
<h3>{{$t('taxBreakdown')}}</h3>
|
||||
<table class="column-oriented">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{$t('type')}}</td>
|
||||
<td class="number">{{$t('taxBase')}}</td>
|
||||
<td width="45%">{{$t('type')}}</td>
|
||||
<td width="20%" class="number">
|
||||
{{$t('taxBase')}}
|
||||
</td>
|
||||
<td>{{$t('tax')}}</td>
|
||||
<td class="number">{{$t('fee')}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="tax in taxes">
|
||||
<td>{{tax.name}}</td>
|
||||
<td class="number">{{tax.Base | currency('EUR', locale)}}</td>
|
||||
<td width="45%">{{tax.name}}</td>
|
||||
<td width="20%" class="number">
|
||||
{{tax.Base | currency('EUR', locale)}}
|
||||
</td>
|
||||
<td>{{tax.vatPercent | percentage}}</td>
|
||||
<td class="number">{{tax.tax | currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="font bold">
|
||||
<td>{{$t('subtotal')}}</td>
|
||||
<td class="number">{{getTotalBase() | currency('EUR', locale)}}</td>
|
||||
<td width="45%">{{$t('subtotal')}}</td>
|
||||
<td width="20%" class="number">
|
||||
{{getTotalBase() | currency('EUR', locale)}}
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="number">{{getTotalTax()| currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
|
@ -204,7 +205,7 @@
|
|||
<!-- End of taxes block -->
|
||||
|
||||
<!-- Packages block -->
|
||||
<div id="packagings" class="size100" v-if="packagings.length > 0">
|
||||
<div id="packagings" class="size100 no-page-break" v-if="packagings.length > 0">
|
||||
<h3>{{$t('packagings')}}</h3>
|
||||
<table class="column-oriented">
|
||||
<thead>
|
||||
|
@ -226,7 +227,7 @@
|
|||
<!-- End of packages block -->
|
||||
|
||||
<!-- Signature block -->
|
||||
<div class="size50 pull-left">
|
||||
<div class="size50 pull-left no-page-break">
|
||||
<div id="signature" class="panel" v-if="signature && signature.id">
|
||||
<div class="header">{{$t('digitalSignature')}}</div>
|
||||
<div class="body centered">
|
||||
|
|
|
@ -84,14 +84,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="non-page-break" v-for="ticket in tickets">
|
||||
<div class="no-page-break" v-for="ticket in tickets">
|
||||
<div>
|
||||
<table class="column-oriented repeatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="number">{{$t('order')}}</td>
|
||||
<td class="number">{{$t('ticket')}}</td>
|
||||
<td>{{$t('client')}}</td>
|
||||
<td width="50%">{{$t('client')}}</td>
|
||||
<td class="number">{{$t('address')}}</td>
|
||||
<td class="number">{{$t('packages')}}</td>
|
||||
</tr>
|
||||
|
@ -100,7 +100,7 @@
|
|||
<tr>
|
||||
<td class="number">{{ticket.priority}}</td>
|
||||
<td class="number">{{ticket.id}}</td>
|
||||
<td>{{ticket.clientFk}} {{ticket.addressName}}</td>
|
||||
<td width="50%">{{ticket.clientFk}} {{ticket.addressName}}</td>
|
||||
<td v-if="ticket.addressFk" class="number">
|
||||
{{ticket.addressFk.toString().substr(0, ticket.addressFk.toString().length - 3)}}
|
||||
<span class="black-container">
|
||||
|
@ -141,7 +141,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th class="font gray align-right">{{$t('import')}}</th>
|
||||
<td>{{ticket.import}}</td>
|
||||
<td>{{ticket.import | currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -63,45 +63,43 @@
|
|||
<table class="column-oriented">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="number">{{$t('boxes')}}</td>
|
||||
<td>{{$t('boxes')}}</td>
|
||||
<td class="number">{{$t('packing')}}</td>
|
||||
<td>{{$t('concept')}}</td>
|
||||
<td width="50%">{{$t('concept')}}</td>
|
||||
<td class="number">{{$t('quantity')}}</td>
|
||||
<td class="number">{{$t('price')}}</td>
|
||||
<td class="number">{{$t('amount')}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="buy in buys">
|
||||
<tr class="font bold">
|
||||
<td class="number">{{buy.box}}</td>
|
||||
<td class="number">{{buy.packing}}</td>
|
||||
<td>{{buy.itemName}}</td>
|
||||
<td class="number">{{buy.quantity}}</td>
|
||||
<td class="number">{{buy.buyingValue | currency('EUR', locale)}}</td>
|
||||
<td class="number">{{buy.buyingValue * buy.quantity | currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
<tr class="description">
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="buy.value5">
|
||||
<strong class="font gray">{{buy.tag5}}</strong>
|
||||
<span>{{buy.value5}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="buy.value6">
|
||||
<strong class="font gray">{{buy.tag6}}</strong>
|
||||
<span>{{buy.value6}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="buy.value7">
|
||||
<strong class="font gray">{{buy.tag7}}</strong>
|
||||
<span>{{buy.value7}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tbody v-for="buy in buys">
|
||||
<tr class="font bold">
|
||||
<td class="number">{{buy.box}}</td>
|
||||
<td class="number">{{buy.packing}}</td>
|
||||
<td width="50%">{{buy.itemName}}</td>
|
||||
<td class="number">{{buy.quantity}}</td>
|
||||
<td class="number">{{buy.buyingValue | currency('EUR', locale)}}</td>
|
||||
<td class="number">{{buy.buyingValue * buy.quantity | currency('EUR', locale)}}</td>
|
||||
</tr>
|
||||
<tr class="description">
|
||||
<td colspan="2">
|
||||
<div v-if="buy.value5">
|
||||
<strong class="font gray">{{buy.tag5}}</strong>
|
||||
<span>{{buy.value5}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2" class="centered">
|
||||
<div v-if="buy.value6">
|
||||
<strong class="font gray">{{buy.tag6}}</strong>
|
||||
<span>{{buy.value6}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="2" class="align-right">
|
||||
<div v-if="buy.value7">
|
||||
<strong class="font gray">{{buy.tag7}}</strong>
|
||||
<span>{{buy.value7}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
|
@ -115,7 +113,7 @@
|
|||
<!-- End of buy block -->
|
||||
<div class="columns">
|
||||
<div class="size50">
|
||||
<div id="notes" class="panel" v-if="entry.notes">
|
||||
<div id="notes" class="panel no-page-break" v-if="entry.notes">
|
||||
<div class="body">
|
||||
<h3>{{$t('notes')}}</h3>
|
||||
<div>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<th>{{client.id}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font gray uppercase">{{$t('dated')}}</td>
|
||||
<td class="font gray uppercase">{{$t('date')}}</td>
|
||||
<th>{{dated}}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -61,8 +61,8 @@
|
|||
<th class="number">{{$t('balance')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="sale in sales" :key="sale.id">
|
||||
<tbody v-for="sale in sales" :key="sale.id">
|
||||
<tr>
|
||||
<td>{{sale.issued | date('%d-%m-%Y')}}</td>
|
||||
<td>{{sale.ref}}</td>
|
||||
<td class="number">{{sale.debtOut}}</td>
|
||||
|
|
|
@ -2,7 +2,7 @@ title: Extracto
|
|||
claimId: Reclamación
|
||||
clientId: Cliente
|
||||
clientData: Datos del cliente
|
||||
dated: Fecha
|
||||
date: Fecha
|
||||
concept: Concepto
|
||||
invoiced: Facturado
|
||||
payed: Pagado
|
||||
|
|
Loading…
Reference in New Issue