Merge branch 'dev' into 3625-ticket_log_routeFk
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2022-03-21 11:56:44 +00:00
commit 728a5de311
37 changed files with 1164 additions and 44 deletions

View File

@ -9,35 +9,35 @@ module.exports = Self => {
accepts: [
{
arg: 'warehouseId',
type: 'Number',
type: 'number',
description: 'The warehouse id',
required: true
}, {
arg: 'companyId',
type: 'Number',
type: 'number',
description: 'The company id',
required: true
}, {
arg: 'dmsTypeId',
type: 'Number',
type: 'number',
description: 'The dms type id',
required: true
}, {
arg: 'reference',
type: 'String',
type: 'string',
required: true
}, {
arg: 'description',
type: 'String',
type: 'string',
required: true
}, {
arg: 'hasFile',
type: 'Boolean',
type: 'boolean',
description: 'True if has an attached file',
required: true
}],
returns: {
type: 'Object',
type: 'object',
root: true
},
http: {

View File

@ -18,9 +18,6 @@
},
"expired": {
"type": "date"
},
"isOfficial": {
"type": "boolean"
}
},

View File

@ -0,0 +1,2 @@
INSERT INTO salix.ACL (id, model, property, accessType, permission, principalType, principalId)
VALUES(301, 'Agency', '*', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,2 @@
INSERT INTO salix.ACL (model,property,accessType,principalId)
VALUES ('AgencyTerm','*','*','administrative');

View File

@ -0,0 +1,10 @@
CREATE TABLE `vn`.`agencyTermConfig` (
`expenceFk` varchar(10) DEFAULT NULL,
`vatAccountSupported` varchar(15) DEFAULT NULL,
`vatPercentage` decimal(28,10) DEFAULT NULL,
`transaction` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `vn`.`agencyTermConfig`
(`expenceFk`, `vatAccountSupported`, `vatPercentage`, `transaction`)
VALUES('6240000000', '4721000015', 21.0000000000, 'Adquisiciones intracomunitarias de servicios');

View File

@ -0,0 +1,3 @@
UPDATE `account`.`user`
SET `role` = 57
WHERE id IN (2294, 4365, 7294);

View File

@ -167,9 +167,11 @@ INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`cod
INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`)
VALUES
(1, 'Pay on receipt', '5720000001', 3, 0, 1, 1),
(2, 'Cash', '5700000001', 2, 0, 1, 1),
(3, 'Compensation', '4000000000', 8, 0, 1, 1);
(1, 'Pay on receipt', '5720000001', 3, 0, 1, 1),
(2, 'Cash', '5700000001', 2, 0, 1, 1),
(3, 'Compensation', '4000000000', 8, 0, 1, 1),
(3117, 'Caixa Rural d''Algemesi', '5720000000', 8, 3117, 1, 1);
INSERT INTO `vn`.`deliveryMethod`(`id`, `code`, `description`)
VALUES
@ -522,6 +524,8 @@ INSERT INTO `vn`.`expence`(`id`, `taxTypeFk`, `name`, `isWithheld`)
(4751000000, 1, 'Retenciones', 1),
(4751000000, 6, 'Retencion', 0),
(6210000567, 0, 'Alquiler VNH', 0),
(6240000000, 1, 'Transportes de ventas rutas', 0),
(6240000000, 4, 'Transportes de ventas rutas', 0),
(7001000000, 1, 'Mercaderia', 0),
(7050000000, 1, 'Prestacion de servicios', 1);
@ -2453,6 +2457,28 @@ INSERT INTO `bs`.`defaulter` (`clientFk`, `amount`, `created`, `defaulterSinced`
(1107, 500, CURDATE(), CURDATE()),
(1109, 500, CURDATE(), CURDATE());
INSERT INTO `vn`.`agencyTerm` (`agencyFk`, `minimumPackages`, `kmPrice`, `packagePrice`, `routePrice`, `minimumKm`, `minimumM3`, `m3Price`)
VALUES
(1, 0, 0.00, 0.00, NULL, 0, 0.00, 0),
(3, 0, 0.00, 3.05, NULL, 0, 0.00, 0),
(2, 60, 0.00, 0.00, NULL, 0, 5.00, 33);
UPDATE `vn`.`agency`
SET `supplierFk`=1
WHERE `id`=1;
UPDATE `vn`.`agency`
SET `supplierFk`=1
WHERE `id`=2;
UPDATE `vn`.`agency`
SET `supplierFk`=2
WHERE `id`=3;
UPDATE `vn`.`route`
SET `invoiceInFk`=1
WHERE `id`=1;
UPDATE `vn`.`route`
SET `invoiceInFk`=2
WHERE `id`=2;
INSERT INTO `bs`.`salesPerson` (`workerFk`, `year`, `month`, `portfolioWeight`)
VALUES
(18, YEAR(CURDATE()), MONTH(CURDATE()), 807.23),

View File

@ -9,7 +9,7 @@ describe('Worker calendar path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSearchResult('Hank Pym');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
@ -18,12 +18,6 @@ describe('Worker calendar path', () => {
});
describe('as hr', () => {
it('should check 5 total holidays have been used so far before testing anything', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 5 ');
});
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
@ -56,14 +50,14 @@ describe('Worker calendar path', () => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 6.5 ');
expect(result).toContain(' 1.5 ');
});
});
describe(`as salesBoss`, () => {
it(`should log in and get to Hank's calendar`, async() => {
it(`should log in and get to Charles Xavier's calendar`, async() => {
await page.loginAndModule('salesBoss', 'worker');
await page.accessToSearchResult('Hank Pym');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
@ -101,14 +95,14 @@ describe('Worker calendar path', () => {
it('should check the total holidays used are back to what it was', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 5 ');
expect(result).toContain(' 0 ');
});
});
describe(`as Hank`, () => {
describe(`as Charles Xavier`, () => {
it(`should log in and get to his calendar`, async() => {
await page.loginAndModule('HankPym', 'worker');
await page.accessToSearchResult('Hank Pym');
await page.loginAndModule('CharlesXavier', 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
@ -122,7 +116,7 @@ describe('Worker calendar path', () => {
it('should check the total holidays used are now the initial ones', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 5 ');
expect(result).toContain(' 0 ');
});
it('should use the year selector to go to the previous year', async() => {

View File

@ -219,5 +219,6 @@
"You can not modify is pay method checked": "No se puede modificar el campo método de pago validado",
"Can't transfer claimed sales": "No puedes transferir lineas reclamadas",
"You don't have privileges to create pay back": "No tienes permisos para crear un abono",
"The item is required": "El artículo es requerido"
"The item is required": "El artículo es requerido",
"reference duplicated": "Referencia duplicada"
}

View File

@ -79,7 +79,6 @@
</vn-autocomplete>
<vn-input-number
vn-one
min="0"
step="0.1"
label="Commission"
ng-model="$ctrl.entry.commission"

View File

@ -7,7 +7,7 @@
"menus": {
"main": [
{"state": "entry.index", "icon": "icon-entry"},
{"state": "entry.latestBuys", "icon": "icon-lastBuy"}
{"state": "entry.latestBuys", "icon": "contact_support"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},

View File

@ -0,0 +1,103 @@
module.exports = Self => {
Self.remoteMethod('createInvoiceIn', {
description: 'Creates an invoiceIn from one or more agency terms',
accessType: 'WRITE',
accepts: [{
arg: 'rows',
type: ['object'],
required: true,
description: `The rows from which the invoiceIn will be created`,
},
{
arg: 'dms',
type: ['object'],
required: true,
description: 'The dms file attached'
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/createInvoiceIn`,
verb: 'POST'
}
});
Self.createInvoiceIn = async(rows, dms, options) => {
const models = Self.app.models;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const [firstRow] = rows;
const [firstDms] = dms;
const reference = await models.Dms.findById([firstDms.id], null, myOptions);
const newInvoiceIn = await models.InvoiceIn.create({
supplierFk: firstRow.supplierFk,
supplierRef: reference.reference,
issued: firstRow.created,
booked: firstRow.created,
operated: firstRow.created,
bookEntried: firstRow.created,
dmsFk: firstDms.id,
}, myOptions);
const expence = await models.AgencyTermConfig.findOne(null, myOptions);
const [taxTypeSage] = await Self.rawSql(`
SELECT IFNULL(s.taxTypeSageFk, CodigoIva) value
FROM vn.supplier s
JOIN sage.TiposIva ti ON TRUE
JOIN vn.agencyTermConfig atg
WHERE s.id = ?
AND ti.CuentaIvaSoportado = atg.vatAccountSupported
AND ti.PorcentajeIva = atg.vatPercentage
`, [firstRow.supplierFk], myOptions);
const [transactionTypeSage] = await Self.rawSql(`
SELECT IFNULL(s.transactionTypeSageFk, tt.CodigoTransaccion) value
FROM vn.supplier s
JOIN sage.TiposTransacciones tt ON TRUE
JOIN vn.agencyTermConfig atg
WHERE s.id = ?
AND tt.Transaccion = atg.transaction
`, [firstRow.supplierFk], myOptions);
await models.InvoiceInTax.create({
invoiceInFk: newInvoiceIn.id,
taxableBase: firstRow.totalPrice,
expenseFk: expence.expenceFk,
taxTypeSageFk: taxTypeSage.value,
transactionTypeSageFk: transactionTypeSage.value
}, myOptions);
await Self.rawSql(`CALL invoiceInDueDay_calculate(?)`, [newInvoiceIn.id], myOptions);
for (let agencyTerm of rows) {
const route = await models.Route.findById(agencyTerm.routeFk, null, myOptions);
await Self.rawSql(`
UPDATE vn.route SET invoiceInFk = ? WHERE id = ?
`, [newInvoiceIn.id, route.id], myOptions);
}
if (tx) await tx.commit();
return newInvoiceIn;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,77 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethodCtx('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmts = [];
const stmt = new ParameterizedSQL(
`SELECT *
FROM (
SELECT r.id routeFk,
r.created,
r.agencyModeFk,
am.name agencyModeName,
am.agencyFk,
a.name agencyAgreement,
SUM(t.packages) packages,
r.m3,
r.kmEnd - r.kmStart kmTotal,
CAST(IFNULL(ate.routePrice,
(ate.kmPrice * (GREATEST(r.kmEnd - r.kmStart , ate.minimumKm))
+ GREATEST(r.m3 , ate.minimumM3) * ate.m3Price)
+ ate.packagePrice * SUM(t.packages) )
AS DECIMAL(10,2)) price,
r.invoiceInFk,
a.supplierFk,
s.name supplierName
FROM vn.route r
LEFT JOIN vn.agencyMode am ON r.agencyModeFk = am.id
LEFT JOIN vn.agency a ON am.agencyFk = a.id
LEFT JOIN vn.ticket t ON t.routeFk = r.id
LEFT JOIN vn.agencyTerm ate ON ate.agencyFk = a.id
LEFT JOIN vn.supplier s ON s.id = a.supplierFk
WHERE r.created > DATE_ADD(CURDATE(), INTERVAL -2 MONTH) AND a.supplierFk IS NOT NULL
GROUP BY r.id
) a`
);
stmt.merge(conn.makeSuffix(filter));
const agencyTerm = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
const models = Self.app.models;
for (let agencyTerm of result)
agencyTerm.route = await models.Route.findById(agencyTerm.routeFk);
return agencyTerm === 0 ? result : result[agencyTerm];
};
};

View File

@ -0,0 +1,48 @@
const models = require('vn-loopback/server/server').models;
// Include after #3638 export database
xdescribe('AgencyTerm createInvoiceIn()', () => {
const rows = [
{
routeFk: 2,
supplierFk: 1,
created: '2022-03-02T23:00:00.000Z',
totalPrice: 165
}
];
const dms = [
{
id: 6
}
];
it('should make an invoiceIn', async() => {
const tx = await models.AgencyTerm.beginTransaction({});
const options = {transaction: tx};
try {
const invoiceInId = 10;
const invoiceInDueDayId = 11;
const invoiceInTaxId = 12;
const oldInvoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
const oldInvoiceInDueDay = await models.InvoiceInDueDay.findById(invoiceInDueDayId, null, options);
const oldInvoiceInTax = await models.InvoiceInTax.findById(invoiceInTaxId, null, options);
await models.AgencyTerm.createInvoiceIn(rows, dms, options);
const [newInvoiceIn] = await models.InvoiceIn.rawSql('SELECT MAX(id) id FROM invoiceIn', null, options);
const [newInvoiceInDueDay] = await models.InvoiceInDueDay.rawSql('SELECT MAX(id) id FROM invoiceInDueDay', null, options);
const [newInvoiceInTax] = await models.InvoiceInTax.rawSql('SELECT MAX(id) id FROM invoiceInTax', null, options);
expect(newInvoiceIn.id).toBeGreaterThan(oldInvoiceIn.id);
expect(newInvoiceInDueDay.id).toBeGreaterThan(oldInvoiceInDueDay.id);
expect(newInvoiceInTax.id).toBeGreaterThan(oldInvoiceInTax.id);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,26 @@
const models = require('vn-loopback/server/server').models;
describe('AgencyTerm filter()', () => {
const authUserId = 9;
it('should return all the tickets matching the filter', async() => {
const tx = await models.AgencyTerm.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const ctx = {req: {accessToken: {userId: authUserId}}};
const agencyTerms = await models.AgencyTerm.filter(ctx, filter, options);
const firstAgencyTerm = agencyTerms[0];
expect(firstAgencyTerm.routeFk).toEqual(1);
expect(agencyTerms.length).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,4 +1,10 @@
{
"AgencyTerm": {
"dataSource": "vn"
},
"AgencyTermConfig": {
"dataSource": "vn"
},
"Route": {
"dataSource": "vn"
},

View File

@ -0,0 +1,24 @@
{
"name": "AgencyTermConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "agencyTermConfig"
}
},
"properties": {
"expenceFk": {
"type": "string",
"id": true
},
"vatAccountSupported": {
"type": "string"
},
"vatPercentage": {
"type": "number"
},
"transaction": {
"type": "string"
}
}
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/agency-term/filter')(Self);
require('../methods/agency-term/createInvoiceIn')(Self);
};

View File

@ -0,0 +1,37 @@
{
"name": "AgencyTerm",
"base": "VnModel",
"options": {
"mysql": {
"table": "agencyTerm"
}
},
"properties": {
"agencyFk": {
"type": "number",
"id": true,
"description": "Identifier"
},
"minimumPackages": {
"type": "number"
},
"kmPrice": {
"type": "number"
},
"packagePrice": {
"type": "number"
},
"routePrice": {
"type": "number"
},
"minimumKm": {
"type": "number"
},
"minimumM3": {
"type": "number"
},
"m3Price": {
"type": "number"
}
}
}

View File

@ -0,0 +1,108 @@
<mg-ajax path="dms/upload" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Companies"
data="companies"
order="code">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses"
order="name">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="DmsTypes"
data="dmsTypes"
order="name">
</vn-crud-model>
<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-textfield
vn-one
vn-focus
label="Reference"
ng-model="$ctrl.dms.reference"
rule>
</vn-textfield>
<vn-autocomplete vn-one
label="Company"
ng-model="$ctrl.dms.companyId"
data="companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="Warehouse"
ng-model="$ctrl.dms.warehouseId"
data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
data="dmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
label="Description"
ng-model="$ctrl.dms.description"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)"
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>
<vn-check
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Upload">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="client.card.dms.index">
</vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,120 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
import UserError from 'core/lib/user-error';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.dms = {
files: [],
hasFile: false,
hasFileAttached: false
};
}
get route() {
return this._route;
}
set route(value) {
this._route = value;
this.setDefaultParams();
this.getAllowedContentTypes();
}
$onChanges() {
if (this.$params && this.$params.q)
this.params = JSON.parse(this.$params.q);
}
getAllowedContentTypes() {
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$t('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const params = {filter: {
where: {code: 'invoiceIn'}
}};
this.$http.get('DmsTypes/findOne', {params}).then(res => {
const dmsType = res.data && res.data;
const companyId = this.vnConfig.companyFk;
const warehouseId = this.vnConfig.warehouseFk;
const defaultParams = {
warehouseId: warehouseId,
companyId: companyId,
dmsTypeId: dmsType.id,
description: this.params.supplierName
};
this.dms = Object.assign(this.dms, defaultParams);
});
}
onSubmit() {
if (this.dms.files.length > 1) throw new UserError('You cannot attach more than one document');
const query = `dms/uploadFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
formData.append(files[0].name, files[0]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
const addedDms = res.data;
this.$.watcher.updateOriginalData();
const params = {
rows: this.params.rows,
dms: addedDms
};
this.$http.post('AgencyTerms/createInvoiceIn', params)
.then(() => {
this.$state.go('route.agencyTerm.index');
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnAgencyTermCreateInvoiceIn', {
template: require('./index.html'),
controller: Controller,
bindings: {
route: '<'
}
});

View File

@ -0,0 +1,107 @@
import './index';
import watcher from 'core/mocks/watcher.js';
describe('AgencyTerm', () => {
describe('Component vnAgencyTermCreateInvoiceIn', () => {
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('route'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
const $element = angular.element('<vn-agency-term-create-invoice-in></vn-agency-term-create-invoice-in>');
controller = $componentController('vnAgencyTermCreateInvoiceIn', {$element});
controller._route = {
id: 1
};
}));
describe('$onChanges()', () => {
it('should update the params data when $params.q is defined', () => {
controller.$params = {q: '{"supplierName": "Plants SL","rows": null}'};
const params = {q: '{"supplierName": "Plants SL", "rows": null}'};
const json = JSON.parse(params.q);
controller.$onChanges();
expect(controller.params).toEqual(json);
});
});
describe('route() setter', () => {
it('should set the ticket data and then call setDefaultParams() and getAllowedContentTypes()', () => {
jest.spyOn(controller, 'setDefaultParams');
jest.spyOn(controller, 'getAllowedContentTypes');
controller.route = {
id: 1
};
expect(controller.route).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const params = {filter: {
where: {code: 'invoiceIn'}
}};
const serializedParams = $httpParamSerializer(params);
$httpBackend.expect('GET', `DmsTypes/findOne?${serializedParams}`).respond({id: 1, code: 'invoiceIn'});
controller.params = {supplierName: 'Plants SL'};
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.dmsTypeId).toEqual(1);
});
});
describe('onSubmit()', () => {
it('should make an HTTP POST request to save the form data', () => {
controller.$.watcher = watcher;
jest.spyOn(controller.$.watcher, 'updateOriginalData');
const files = [{id: 1, name: 'MyFile'}];
controller.dms = {files};
const serializedParams = $httpParamSerializer(controller.dms);
const query = `dms/uploadFile?${serializedParams}`;
controller.params = {rows: null};
$httpBackend.expect('POST', query).respond({});
$httpBackend.expect('POST', 'AgencyTerms/createInvoiceIn').respond({});
controller.onSubmit();
$httpBackend.flush();
});
});
describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
});
});

View File

@ -0,0 +1,7 @@
vn-ticket-request {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -0,0 +1,143 @@
<vn-crud-model
vn-id="model"
url="AgencyTerms/filter"
filter="::$ctrl.filter"
data="agencyTerms"
auto-load="true">
</vn-crud-model>
<vn-card>
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<div>
<div class="totalBox" style="text-align: center;">
<h6 translate>Total</h6>
<vn-label-value
label="Price"
value="{{$ctrl.totalPrice | currency: 'EUR': 2}}">
</vn-label-value>
</div>
</div>
</slot-actions>
<slot-table>
<table>
<thead>
<tr>
<th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</th>
<th shrink field="routeFk">
<span translate>Id</span>
</th>
<th field="created">
<span translate>Date</span>
</th>
<th field="agencyFk">
<span translate>Agency route</span>
</th>
<th field="agencyAgreement">
<span translate>Agency Agreement</span>
</th>
<th field="packages">
<span translate>Packages</span>
</th>
<th field="m3">
<span translate>M3</span>
</th>
<th field="kmTotal">
<span translate>Km</span>
</th>
<th field="price">
<span translate>Price</span>
</th>
<th field="invoiceInFk">
<span translate>Received</span>
</th>
<th field="supplierFk">
<span translate>Autonomous</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="agencyTerm in agencyTerms">
<td shrink>
<vn-check
ng-model="agencyTerm.checked"
vn-click-stop>
</vn-check>
</td>
<td>
<span
title="{{::agencyTerm.id}}"
vn-click-stop="routeDescriptor.show($event, agencyTerm.routeFk)"
class="link">
{{::agencyTerm.routeFk}}
</span>
</td>
<td shrink-date>{{::agencyTerm.created | date:'dd/MM/yyyy'}}</td>
<td>{{::agencyTerm.agencyModeName | dashIfEmpty}}</td>
<td>{{::agencyTerm.agencyAgreement | dashIfEmpty}}</td>
<td>{{::agencyTerm.packages | dashIfEmpty}}</td>
<td>{{::agencyTerm.m3 | dashIfEmpty}}</td>
<td>{{::agencyTerm.kmTotal | dashIfEmpty}}</td>
<td>{{::agencyTerm.price | dashIfEmpty}}</td>
<td>
<span
vn-click-stop="invoiceInDescriptor.show($event, agencyTerm.invoiceInFk)"
class="link">
{{::agencyTerm.invoiceInFk}}
</span>
</td>
<td>
<span
class="link"
vn-click-stop="supplierDescriptor.show($event, agencyTerm.supplierFk)">
{{::agencyTerm.supplierName}}
</span>
</td>
<td>
<vn-icon-button
vn-click-stop="$ctrl.preview(agencyTerm.route)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-popup vn-id="summary">
<vn-route-summary
route="$ctrl.routeSelected">
</vn-route-summary>
</vn-popup>
<vn-route-descriptor-popover
vn-id="routeDescriptor">
</vn-route-descriptor-popover>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>
<vn-invoice-in-descriptor-popover
vn-id="invoiceInDescriptor">
</vn-invoice-in-descriptor-popover>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<vn-button class="round sm vn-mb-sm"
icon="icon-invoice-in-create"
ng-show="$ctrl.totalChecked > 0"
ng-click="$ctrl.createInvoiceIn()"
vn-tooltip="Create invoiceIn"
tooltip-position="left">
</vn-button>
</vn-vertical>
</div>

View File

@ -0,0 +1,122 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'agencyFk',
autocomplete: {
url: 'AgencyModes',
showField: 'name',
valueField: 'name'
}
},
{
field: 'agencyAgreement',
autocomplete: {
url: 'Agencies',
showField: 'name',
valueField: 'name'
}
},
{
field: 'supplierFk',
autocomplete: {
url: 'Suppliers',
showField: 'name',
valueField: 'name',
}
}
]
};
}
exprBuilder(param, value) {
switch (param) {
case 'agencyFk':
return {'a.agencyModeName': value};
case 'supplierFk':
return {'a.supplierName': value};
case 'routeFk':
return {'a.routeFk': value};
case 'created':
case 'agencyAgreement':
case 'packages':
case 'm3':
case 'kmTotal':
case 'price':
case 'invoiceInFk':
return {[`a.${param}`]: value};
}
}
get checked() {
const agencyTerms = this.$.model.data || [];
const checkedAgencyTerms = [];
for (let agencyTerm of agencyTerms) {
if (agencyTerm.checked)
checkedAgencyTerms.push(agencyTerm);
}
return checkedAgencyTerms;
}
get totalChecked() {
return this.checked.length;
}
get totalPrice() {
let totalPrice = 0;
if (this.checked.length > 0) {
for (let agencyTerm of this.checked)
totalPrice += agencyTerm.price;
return totalPrice;
}
return totalPrice;
}
preview(route) {
this.routeSelected = route;
this.$.summary.show();
}
createInvoiceIn() {
const rowsToCreateInvoiceIn = [];
const supplierFk = this.checked[0].supplierFk;
for (let agencyTerm of this.checked) {
let hasSameSupplier = supplierFk == agencyTerm.supplierFk;
if (hasSameSupplier) {
rowsToCreateInvoiceIn.push({
routeFk: agencyTerm.routeFk,
supplierFk: agencyTerm.supplierFk,
created: agencyTerm.created,
totalPrice: this.totalPrice});
} else {
this.vnApp.showError(this.$t('Two autonomous cannot be counted at the same time'));
return false;
}
}
const params = JSON.stringify({
supplierName: this.checked[0].supplierName,
rows: rowsToCreateInvoiceIn
});
this.$state.go('route.agencyTerm.createInvoiceIn', {q: params});
}
}
ngModule.vnComponent('vnAgencyTermIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,94 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('AgencyTerm', () => {
describe('Component vnAgencyTermIndex', () => {
let controller;
let $window;
beforeEach(ngModule('route'));
beforeEach(inject(($componentController, _$window_) => {
$window = _$window_;
const $element = angular.element('<vn-agency-term-index></vn-agency-term-index>');
controller = $componentController('vnAgencyTermIndex', {$element});
controller.$.model = crudModel;
controller.$.model.data = [
{supplierFk: 1, totalPrice: null},
{supplierFk: 1},
{supplierFk: 2}
];
}));
describe('checked() getter', () => {
it('should return the checked lines', () => {
const data = controller.$.model.data;
data[0].checked = true;
const checkedRows = controller.checked;
const firstCheckedRow = checkedRows[0];
expect(firstCheckedRow.supplierFk).toEqual(1);
});
});
describe('totalCheked() getter', () => {
it('should return the total checked lines', () => {
const data = controller.$.model.data;
data[0].checked = true;
const checkedRows = controller.totalChecked;
expect(checkedRows).toEqual(1);
});
});
describe('preview()', () => {
it('should show the summary dialog', () => {
controller.$.summary = {show: () => {}};
jest.spyOn(controller.$.summary, 'show');
let event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
const route = {id: 1};
controller.preview(event, route);
expect(controller.$.summary.show).toHaveBeenCalledWith();
});
});
describe('createInvoiceIn()', () => {
it('should throw an error if more than one autonomous are checked', () => {
jest.spyOn(controller.vnApp, 'showError');
const data = controller.$.model.data;
data[0].checked = true;
data[2].checked = true;
controller.createInvoiceIn();
expect(controller.vnApp.showError).toHaveBeenCalled();
});
it('should call the function go() on $state to go to the file management', () => {
jest.spyOn(controller.$state, 'go');
const data = controller.$.model.data;
data[0].checked = true;
controller.createInvoiceIn();
delete data[0].checked;
const params = JSON.stringify({
supplierName: data[0].supplierName,
rows: [data[0]]
});
expect(controller.$state.go).toHaveBeenCalledWith('route.agencyTerm.createInvoiceIn', {q: params});
});
});
});
});

View File

@ -0,0 +1,32 @@
@import "variables";
vn-item-product {
display: block;
.id {
background-color: $color-main;
color: $color-font-dark;
margin-bottom: 0;
}
.image {
height: 112px;
width: 112px;
& > img {
max-height: 100%;
max-width: 100%;
border-radius: 3px;
}
}
vn-label-value:first-of-type section{
margin-top: 9px;
}
}
table {
img {
border-radius: 50%;
width: 50px;
height: 50px;
}
}

View File

@ -0,0 +1,5 @@
Agency route: Agencia ruta
Agency Agreement: Acuerdo agencia
Autonomous: Autónomos
Two autonomous cannot be counted at the same time: Dos autonónomos no pueden ser contabilizados al mismo tiempo
You cannot attach more than one document: No puedes adjuntar más de un documento

View File

@ -11,4 +11,6 @@ import './create';
import './basic-data';
import './log';
import './tickets';
import './agency-term/index';
import './agency-term/createInvoiceIn';
import './ticket-popup';

View File

@ -3,10 +3,11 @@
"name": "Routes",
"icon": "icon-delivery",
"validations" : true,
"dependencies": ["client", "worker", "ticket"],
"dependencies": ["client", "worker", "ticket", "supplier", "invoiceIn"],
"menus": {
"main": [
{"state": "route.index", "icon": "icon-delivery"}
{"state": "route.index", "icon": "icon-delivery"},
{"state": "route.agencyTerm.index", "icon": "contact_support"}
],
"card": [
{"state": "route.card.basicData", "icon": "settings"},
@ -37,6 +38,26 @@
"state": "route.card",
"abstract": true,
"component": "vn-route-card"
}, {
"url": "/agency-term",
"abstract": true,
"state": "route.agencyTerm",
"component": "ui-view"
}, {
"url": "/index",
"state": "route.agencyTerm.index",
"component": "vn-agency-term-index",
"description": "Autonomous",
"acl": ["administrative"]
},{
"url": "/createInvoiceIn?q",
"state": "route.agencyTerm.createInvoiceIn",
"component": "vn-agency-term-create-invoice-in",
"description": "File management",
"params": {
"route": "$ctrl.route"
},
"acl": ["administrative"]
}, {
"url": "/summary",
"state": "route.card.summary",

View File

@ -116,8 +116,8 @@ module.exports = Self => {
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss', myOptions);
if (!isProductionBoss) {
const isDeliveryBoss = await models.Account.hasRole(userId, 'deliveryBoss', myOptions);
if (!isDeliveryBoss) {
const zoneShipped = await models.Agency.getShipped(
args.landed,
args.addressFk,

View File

@ -31,10 +31,10 @@ module.exports = Self => {
}, myOptions);
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant', myOptions);
const isProductionBoss = await Self.app.models.Account.hasRole(userId, 'productionBoss', myOptions);
const isDeliveryBoss = await Self.app.models.Account.hasRole(userId, 'deliveryBoss', myOptions);
const isBuyer = await Self.app.models.Account.hasRole(userId, 'buyer', myOptions);
const isValidRole = isSalesAssistant || isProductionBoss || isBuyer;
const isValidRole = isSalesAssistant || isDeliveryBoss || isBuyer;
let alertLevel = state ? state.alertLevel : null;
let ticket = await Self.app.models.Ticket.findById(id, {

View File

@ -78,8 +78,8 @@ module.exports = Self => {
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss', myOptions);
if (!isProductionBoss) {
const isDeliveryBoss = await models.Account.hasRole(userId, 'deliveryBoss', myOptions);
if (!isDeliveryBoss) {
const zoneShipped = await models.Agency.getShipped(
args.landed,
args.addressId,

View File

@ -24,7 +24,7 @@
<vn-th field="stateFk" >State</vn-th>
<vn-th field="zoneFk">Zone</vn-th>
<vn-th field="warehouseFk">Warehouse</vn-th>
<vn-th number>Total</vn-th>
<vn-th field="totalWithVat" number>Total</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>

View File

@ -69,14 +69,14 @@ class Controller extends Section {
if (!$events.length) return;
const day = $days[0];
const zonesIds = [];
const zoneIds = [];
for (let event of $events)
zonesIds.push(event.zoneFk);
zoneIds.push(event.zoneFk);
this.$.zoneEvents.show($event.target);
const params = {
zonesId: zonesIds,
zoneIds: zoneIds,
date: day
};

View File

@ -110,7 +110,7 @@ describe('Zone Component vnZoneDeliveryDays', () => {
{zoneFk: 8}
];
const params = {
zonesId: [1, 2, 8],
zoneIds: [1, 2, 8],
date: [day][0]
};
const response = [{id: 1, hour: ''}];

View File