Merge branch 'dev' into 4040-chat_sendQueued
gitea/salix/pipeline/head This commit is unstable
Details
gitea/salix/pipeline/head This commit is unstable
Details
This commit is contained in:
commit
cd5dba6fcb
|
@ -5,17 +5,17 @@ module.exports = Self => {
|
|||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
type: 'number',
|
||||
description: 'The user id',
|
||||
http: {source: 'path'}
|
||||
}, {
|
||||
arg: 'oldPassword',
|
||||
type: 'String',
|
||||
type: 'string',
|
||||
description: 'The old password',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'newPassword',
|
||||
type: 'String',
|
||||
type: 'string',
|
||||
description: 'The new password',
|
||||
required: true
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE bucket
|
||||
INTO TABLE `edi`.`bucket`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE bucket_type
|
||||
INTO TABLE `edi`.`bucket_type`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE `feature`
|
||||
INTO TABLE `edi`.`feature`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE genus
|
||||
INTO TABLE `edi`.`genus`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE item
|
||||
INTO TABLE `edi`.`item`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE `item_feature`
|
||||
INTO TABLE `edi`.`item_feature`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE item_group
|
||||
INTO TABLE `edi`.`item_group`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE plant
|
||||
INTO TABLE `edi`.`plant`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE specie
|
||||
INTO TABLE `edi`.`specie`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE edi.supplier
|
||||
INTO TABLE `edi`.`supplier`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12, @col13, @col14, @col15, @col16, @col17, @col18, @col19, @col20)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE `type`
|
||||
INTO TABLE `edi`.`type`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
|
||||
SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
LOAD DATA LOCAL INFILE ?
|
||||
INTO TABLE `value`
|
||||
INTO TABLE `edi`.`value`
|
||||
FIELDS TERMINATED BY ';'
|
||||
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7)
|
||||
SET
|
||||
|
|
|
@ -22,6 +22,9 @@ module.exports = Self => {
|
|||
const container = await models.TempContainer.container('edi');
|
||||
const tempPath = path.join(container.client.root, container.name);
|
||||
|
||||
// Temporary file clean
|
||||
await fs.rmdir(`${tempPath}/*`, {recursive: true});
|
||||
|
||||
const [ftpConfig] = await Self.rawSql('SELECT host, user, password FROM edi.ftpConfig');
|
||||
console.debug(`Openning FTP connection to ${ftpConfig.host}...\n`);
|
||||
|
||||
|
@ -48,6 +51,12 @@ module.exports = Self => {
|
|||
tempDir = `${tempPath}/${fileName}`;
|
||||
tempFile = `${tempPath}/${fileName}.zip`;
|
||||
|
||||
// if (fs.existsSync(tempFile))
|
||||
// await fs.unlink(tempFile);
|
||||
|
||||
// if (fs.existsSync(tempDir))
|
||||
// await fs.rmdir(tempDir, {recursive: true});
|
||||
|
||||
await extractFile({
|
||||
ftpClient: ftpClient,
|
||||
file: file,
|
||||
|
@ -61,7 +70,6 @@ module.exports = Self => {
|
|||
if (fs.existsSync(tempFile))
|
||||
await fs.unlink(tempFile);
|
||||
|
||||
await fs.rmdir(tempDir, {recursive: true});
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
@ -86,9 +94,6 @@ module.exports = Self => {
|
|||
|
||||
zip.extractAllTo(paths.tempDir, false);
|
||||
|
||||
if (fs.existsSync(paths.tempFile))
|
||||
await fs.unlink(paths.tempFile);
|
||||
|
||||
await dumpData({file, entries, paths});
|
||||
|
||||
await fs.rmdir(paths.tempDir, {recursive: true});
|
||||
|
@ -99,57 +104,59 @@ module.exports = Self => {
|
|||
const toTable = file.toTable;
|
||||
const baseName = file.fileName;
|
||||
|
||||
for (const zipEntry of entries) {
|
||||
const entryName = zipEntry.entryName;
|
||||
console.log(`Reading file ${entryName}...`);
|
||||
const tx = await Self.beginTransaction({});
|
||||
|
||||
const startIndex = (entryName.length - 10);
|
||||
const endIndex = (entryName.length - 4);
|
||||
const dateString = entryName.substring(startIndex, endIndex);
|
||||
const lastUpdated = new Date();
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
// Format string date to a date object
|
||||
let updated = null;
|
||||
if (file.updated) {
|
||||
updated = new Date(file.updated);
|
||||
updated.setHours(0, 0, 0, 0);
|
||||
}
|
||||
const tableName = `edi.${toTable}`;
|
||||
await Self.rawSql(`DELETE FROM ??`, [tableName], options);
|
||||
|
||||
lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`);
|
||||
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1);
|
||||
lastUpdated.setDate(dateString.substring(0, 2));
|
||||
lastUpdated.setHours(0, 0, 0, 0);
|
||||
for (const zipEntry of entries) {
|
||||
const entryName = zipEntry.entryName;
|
||||
console.log(`Reading file ${entryName}...`);
|
||||
|
||||
if (updated && lastUpdated <= updated) {
|
||||
console.debug(`Table ${toTable} already updated, skipping...`);
|
||||
continue;
|
||||
}
|
||||
const startIndex = (entryName.length - 10);
|
||||
const endIndex = (entryName.length - 4);
|
||||
const dateString = entryName.substring(startIndex, endIndex);
|
||||
const lastUpdated = new Date();
|
||||
|
||||
console.log('Dumping data...');
|
||||
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
|
||||
const sqlTemplate = fs.readFileSync(templatePath, 'utf8');
|
||||
// Format string date to a date object
|
||||
let updated = null;
|
||||
if (file.updated) {
|
||||
updated = new Date(file.updated);
|
||||
updated.setHours(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
const rawPath = path.join(paths.tempDir, entryName);
|
||||
lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`);
|
||||
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1);
|
||||
lastUpdated.setDate(dateString.substring(0, 2));
|
||||
lastUpdated.setHours(0, 0, 0, 0);
|
||||
|
||||
try {
|
||||
const tx = await Self.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
if (updated && lastUpdated <= updated) {
|
||||
console.debug(`Table ${toTable} already updated, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('Dumping data...');
|
||||
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
|
||||
const sqlTemplate = fs.readFileSync(templatePath, 'utf8');
|
||||
|
||||
const rawPath = path.join(paths.tempDir, entryName);
|
||||
|
||||
await Self.rawSql(`DELETE FROM edi.${toTable}`, null, options);
|
||||
await Self.rawSql(sqlTemplate, [rawPath], options);
|
||||
await Self.rawSql(`
|
||||
UPDATE edi.fileConfig
|
||||
SET updated = ?
|
||||
WHERE fileName = ?
|
||||
`, [lastUpdated, baseName], options);
|
||||
|
||||
tx.commit();
|
||||
} catch (error) {
|
||||
tx.rollback();
|
||||
throw error;
|
||||
UPDATE edi.fileConfig
|
||||
SET updated = ?
|
||||
WHERE fileName = ?
|
||||
`, [lastUpdated, baseName], options);
|
||||
}
|
||||
|
||||
console.log(`Updated table ${toTable}\n`);
|
||||
tx.commit();
|
||||
} catch (error) {
|
||||
tx.rollback();
|
||||
throw error;
|
||||
}
|
||||
console.log(`Updated table ${toTable}\n`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE `vn`.`mdbBranch` (
|
||||
`name` VARCHAR(255),
|
||||
PRIMARY KEY(`name`)
|
||||
);
|
||||
|
||||
CREATE TABLE `vn`.`mdbVersion` (
|
||||
`app` VARCHAR(255) NOT NULL,
|
||||
`branchFk` VARCHAR(255) NOT NULL,
|
||||
`version` INT,
|
||||
CONSTRAINT `mdbVersion_branchFk` FOREIGN KEY (`branchFk`) REFERENCES `vn`.`mdbBranch` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES('MdbVersion', '*', '*', 'ALLOW', 'ROLE', 'developer');
|
|
@ -0,0 +1,3 @@
|
|||
INSERT INTO `salix`.`defaultViewConfig` (tableCode, columns)
|
||||
VALUES ('clientsDetail', '{"id":true,"phone":true,"city":true,"socialName":true,"salesPersonFk":true,"email":true,"name":false,"fi":false,"credit":false,"creditInsurance":false,"mobile":false,"street":false,"countryFk":false,"provinceFk":false,"postcode":false,"created":false,"businessTypeFk":false,"payMethodFk":false,"sageTaxTypeFk":false,"sageTransactionTypeFk":false,"isActive":false,"isVies":false,"isTaxDataChecked":false,"isEqualizated":false,"isFreezed":false,"hasToInvoice":false,"hasToInvoiceByAddress":false,"isToBeMailed":false,"hasLcr":false,"hasCoreVnl":false,"hasSepaVnl":false}');
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
DROP PROCEDURE IF EXISTS vn.ticket_doRefund;
|
||||
DROP PROCEDURE IF EXISTS `vn`.`ticket_doRefund`;
|
||||
|
||||
DELIMITER $$
|
||||
$$
|
|
@ -2583,3 +2583,12 @@ INSERT INTO `vn`.`machineWorker` (`workerFk`, `machineFk`, `inTimed`, `outTimed`
|
|||
(1106, 2, CURDATE(), NULL),
|
||||
(1106, 2, DATE_ADD(CURDATE(), INTERVAL + 1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY));
|
||||
|
||||
INSERT INTO `vn`.`mdbBranch` (`name`)
|
||||
VALUES
|
||||
('test'),
|
||||
('master');
|
||||
|
||||
INSERT INTO `vn`.`mdbVersion` (`app`, `branchFk`, `version`)
|
||||
VALUES
|
||||
('tpv', 'test', '1'),
|
||||
('lab', 'master', '1');
|
|
@ -32,6 +32,7 @@ services:
|
|||
- /mnt/appdata/pdfs:/var/lib/salix/pdfs
|
||||
- /mnt/appdata/dms:/var/lib/salix/dms
|
||||
- /mnt/appdata/image:/var/lib/salix/image
|
||||
- /mnt/appdata/vn-access:/var/lib/salix/vn-access
|
||||
deploy:
|
||||
replicas: ${BACK_REPLICAS:?}
|
||||
placement:
|
||||
|
|
|
@ -123,19 +123,19 @@ describe('Client lock verified data path', () => {
|
|||
await page.accessToSection('client.card.fiscalData');
|
||||
}, 20000);
|
||||
|
||||
it('should confirm verified data button is disabled for salesAssistant', async() => {
|
||||
it('should confirm verified data button is enabled for salesAssistant', async() => {
|
||||
const isDisabled = await page.isDisabled(selectors.clientFiscalData.verifiedDataCheckbox);
|
||||
|
||||
expect(isDisabled).toBeTrue();
|
||||
expect(isDisabled).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return error when edit the social name', async() => {
|
||||
it('should now edit the social name', async() => {
|
||||
await page.clearInput(selectors.clientFiscalData.socialName);
|
||||
await page.write(selectors.clientFiscalData.socialName, 'new social name edition');
|
||||
await page.waitToClick(selectors.clientFiscalData.saveButton);
|
||||
const message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain(`Not enough privileges to edit a client with verified data`);
|
||||
expect(message.text).toContain(`Data saved!`);
|
||||
});
|
||||
|
||||
it('should now confirm the social name have been edited once and for all', async() => {
|
||||
|
|
|
@ -318,6 +318,8 @@ export default class SmartTable extends Component {
|
|||
for (let column of columns) {
|
||||
const field = column.getAttribute('field');
|
||||
const cell = document.createElement('td');
|
||||
cell.setAttribute('centered', '');
|
||||
|
||||
if (field) {
|
||||
let input;
|
||||
let options;
|
||||
|
@ -331,6 +333,15 @@ export default class SmartTable extends Component {
|
|||
continue;
|
||||
}
|
||||
|
||||
input = this.$compile(`
|
||||
<vn-textfield
|
||||
class="dense"
|
||||
name="${field}"
|
||||
ng-model="searchProps['${field}']"
|
||||
ng-keydown="$ctrl.searchWithEvent($event, '${field}')"
|
||||
clear-disabled="true"
|
||||
/>`)(this.$inputsScope);
|
||||
|
||||
if (options && options.autocomplete) {
|
||||
let props = ``;
|
||||
|
||||
|
@ -346,16 +357,29 @@ export default class SmartTable extends Component {
|
|||
on-change="$ctrl.searchByColumn('${field}')"
|
||||
clear-disabled="true"
|
||||
/>`)(this.$inputsScope);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (options && options.checkbox) {
|
||||
input = this.$compile(`
|
||||
<vn-textfield
|
||||
<vn-check
|
||||
class="dense"
|
||||
name="${field}"
|
||||
ng-model="searchProps['${field}']"
|
||||
ng-keydown="$ctrl.searchWithEvent($event, '${field}')"
|
||||
clear-disabled="true"
|
||||
on-change="$ctrl.searchByColumn('${field}')"
|
||||
triple-state="true"
|
||||
/>`)(this.$inputsScope);
|
||||
}
|
||||
|
||||
if (options && options.datepicker) {
|
||||
input = this.$compile(`
|
||||
<vn-date-picker
|
||||
class="dense"
|
||||
name="${field}"
|
||||
ng-model="searchProps['${field}']"
|
||||
on-change="$ctrl.searchByColumn('${field}')"
|
||||
/>`)(this.$inputsScope);
|
||||
}
|
||||
|
||||
cell.appendChild(input[0]);
|
||||
}
|
||||
searchRow.appendChild(cell);
|
||||
|
@ -372,13 +396,12 @@ export default class SmartTable extends Component {
|
|||
|
||||
searchByColumn(field) {
|
||||
const searchCriteria = this.$inputsScope.searchProps[field];
|
||||
const emptySearch = searchCriteria == '' || null;
|
||||
const emptySearch = searchCriteria === '' || searchCriteria == null;
|
||||
|
||||
const filters = this.filterSanitizer(field);
|
||||
|
||||
if (filters && filters.userFilter)
|
||||
this.model.userFilter = filters.userFilter;
|
||||
|
||||
if (!emptySearch)
|
||||
this.addFilter(field, this.$inputsScope.searchProps[field]);
|
||||
else this.model.refresh();
|
||||
|
|
|
@ -224,5 +224,7 @@
|
|||
"The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo",
|
||||
"date in the future": "Fecha en el futuro",
|
||||
"reference duplicated": "Referencia duplicada",
|
||||
"This ticket is already a refund": "Este ticket ya es un abono"
|
||||
"This ticket is already a refund": "Este ticket ya es un abono",
|
||||
"isWithoutNegatives": "isWithoutNegatives",
|
||||
"routeFk": "routeFk"
|
||||
}
|
|
@ -98,5 +98,15 @@
|
|||
"image/jpg",
|
||||
"video/mp4"
|
||||
]
|
||||
},
|
||||
"accessStorage": {
|
||||
"name": "accessStorage",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"root": "./storage/access",
|
||||
"maxFileSize": "524288000",
|
||||
"allowedContentTypes": [
|
||||
"application/x-7z-compressed"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -86,7 +86,6 @@ module.exports = Self => {
|
|||
};
|
||||
ticketFk = await createTicket(ctx, myOptions);
|
||||
}
|
||||
|
||||
await models.Sale.create({
|
||||
ticketFk: ticketFk,
|
||||
itemFk: sale.itemFk,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<vn-crud-model vn-id="model"
|
||||
url="ClaimDms"
|
||||
filter="::$ctrl.filter"
|
||||
data="photos">
|
||||
</vn-crud-model>
|
||||
<vn-card class="summary">
|
||||
|
@ -106,8 +107,13 @@
|
|||
<section class="photo" ng-repeat="photo in photos">
|
||||
<section class="image" on-error-src
|
||||
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
||||
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}">
|
||||
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"
|
||||
ng-if="photo.dms.contentType != 'video/mp4'">
|
||||
</section>
|
||||
<video id="videobcg" muted="muted" controls ng-if="photo.dms.contentType == 'video/mp4'"
|
||||
class="video">
|
||||
<source src="{{$ctrl.getImagePath(photo.dmsFk)}}" type="video/mp4">
|
||||
</video>
|
||||
</section>
|
||||
</vn-horizontal>
|
||||
</vn-auto>
|
||||
|
|
|
@ -6,6 +6,13 @@ class Controller extends Summary {
|
|||
constructor($element, $, vnFile) {
|
||||
super($element, $);
|
||||
this.vnFile = vnFile;
|
||||
this.filter = {
|
||||
include: [
|
||||
{
|
||||
relation: 'dms'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
$onChanges() {
|
||||
|
|
|
@ -10,4 +10,19 @@ vn-claim-summary {
|
|||
vn-textarea *{
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
|
||||
0 3px 1px -2px rgba(0,0,0,.2),
|
||||
0 1px 5px 0 rgba(0,0,0,.12);
|
||||
border: 2px solid transparent;
|
||||
|
||||
}
|
||||
.video:hover {
|
||||
border: 2px solid $color-primary
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('extendedListFilter', {
|
||||
description: 'Find all clients matched by the filter',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
},
|
||||
{
|
||||
arg: 'search',
|
||||
type: 'string',
|
||||
description: `If it's and integer searchs by id, otherwise it searchs by name`,
|
||||
},
|
||||
{
|
||||
arg: 'name',
|
||||
type: 'string',
|
||||
description: 'The client name',
|
||||
},
|
||||
{
|
||||
arg: 'salesPersonFk',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
arg: 'fi',
|
||||
type: 'string',
|
||||
description: 'The client fiscal id',
|
||||
},
|
||||
{
|
||||
arg: 'socialName',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
arg: 'city',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
arg: 'postcode',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
arg: 'provinceFk',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
arg: 'email',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
arg: 'phone',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/extendedListFilter`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.extendedListFilter = async(ctx, filter, options) => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const where = buildFilter(ctx.args, (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return /^\d+$/.test(value)
|
||||
? {'c.id': {inq: value}}
|
||||
: {'c.name': {like: `%${value}%`}};
|
||||
case 'name':
|
||||
case 'salesPersonFk':
|
||||
case 'fi':
|
||||
case 'socialName':
|
||||
case 'city':
|
||||
case 'postcode':
|
||||
case 'provinceFk':
|
||||
case 'email':
|
||||
case 'phone':
|
||||
param = `c.${param}`;
|
||||
return {[param]: value};
|
||||
}
|
||||
});
|
||||
|
||||
filter = mergeFilters(filter, {where});
|
||||
|
||||
const stmts = [];
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.socialName,
|
||||
c.fi,
|
||||
c.credit,
|
||||
c.creditInsurance,
|
||||
c.phone,
|
||||
c.mobile,
|
||||
c.street,
|
||||
c.city,
|
||||
c.postcode,
|
||||
c.email,
|
||||
c.created,
|
||||
c.isActive,
|
||||
c.isVies,
|
||||
c.isTaxDataChecked,
|
||||
c.isEqualizated,
|
||||
c.isFreezed,
|
||||
c.hasToInvoice,
|
||||
c.hasToInvoiceByAddress,
|
||||
c.isToBeMailed,
|
||||
c.hasSepaVnl,
|
||||
c.hasLcr,
|
||||
c.hasCoreVnl,
|
||||
ct.id AS countryFk,
|
||||
ct.country,
|
||||
p.id AS provinceFk,
|
||||
p.name AS province,
|
||||
u.id AS salesPersonFk,
|
||||
u.name AS salesPerson,
|
||||
bt.code AS businessTypeFk,
|
||||
bt.description AS businessType,
|
||||
pm.id AS payMethodFk,
|
||||
pm.name AS payMethod,
|
||||
sti.CodigoIva AS sageTaxTypeFk,
|
||||
sti.Iva AS sageTaxType,
|
||||
stt.CodigoTransaccion AS sageTransactionTypeFk,
|
||||
stt.Transaccion AS sageTransactionType
|
||||
FROM client c
|
||||
LEFT JOIN account.user u ON u.id = c.salesPersonFk
|
||||
LEFT JOIN country ct ON ct.id = c.countryFk
|
||||
LEFT JOIN province p ON p.id = c.provinceFk
|
||||
LEFT JOIN businessType bt ON bt.code = c.businessTypeFk
|
||||
LEFT JOIN payMethod pm ON pm.id = c.payMethodFk
|
||||
LEFT JOIN sage.TiposIva sti ON sti.CodigoIva = c.taxTypeSageFk
|
||||
LEFT JOIN sage.TiposTransacciones stt ON stt.CodigoTransaccion = c.transactionTypeSageFk
|
||||
`
|
||||
);
|
||||
|
||||
stmt.merge(conn.makeWhere(filter.where));
|
||||
stmt.merge(conn.makePagination(filter));
|
||||
|
||||
const clientsIndex = stmts.push(stmt) - 1;
|
||||
const sql = ParameterizedSQL.join(stmts, ';');
|
||||
const result = await conn.executeStmt(sql, myOptions);
|
||||
|
||||
return clientsIndex === 0 ? result : result[clientsIndex];
|
||||
};
|
||||
};
|
|
@ -0,0 +1,180 @@
|
|||
const { models } = require('vn-loopback/server/server');
|
||||
|
||||
describe('client extendedListFilter()', () => {
|
||||
it('should return the clients matching the filter with a limit of 20 rows', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {}};
|
||||
const filter = {limit: '20'};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(20);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the client "Bruce Wayne" matching the search argument with his name', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {search: 'Bruce Wayne'}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const firstResult = result[0];
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
expect(firstResult.name).toEqual('Bruce Wayne');
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the client "Bruce Wayne" matching the search argument with his id', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {search: '1101'}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const firstResult = result[0];
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
expect(firstResult.name).toEqual('Bruce Wayne');
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the client "Bruce Wayne" matching the name argument', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {name: 'Bruce Wayne'}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const firstResult = result[0];
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
expect(firstResult.name).toEqual('Bruce Wayne');
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the clients matching the "salesPersonFk" argument', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
const salesPersonId = 18;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {salesPersonFk: salesPersonId}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * result.length);
|
||||
const randomResultClient = result[randomIndex];
|
||||
|
||||
expect(result.length).toBeGreaterThanOrEqual(5);
|
||||
expect(randomResultClient.salesPersonFk).toEqual(salesPersonId);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the clients matching the "fi" argument', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {fi: '251628698'}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const firstClient = result[0];
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
expect(firstClient.name).toEqual('Max Eisenhardt');
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the clients matching the "city" argument', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Silla'}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * result.length);
|
||||
const randomResultClient = result[randomIndex];
|
||||
|
||||
expect(result.length).toBeGreaterThanOrEqual(20);
|
||||
expect(randomResultClient.city.toLowerCase()).toEqual('silla');
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the clients matching the "postcode" argument', async() => {
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}, args: {postcode: '46460'}};
|
||||
const filter = {};
|
||||
const result = await models.Client.extendedListFilter(ctx, filter, options);
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * result.length);
|
||||
const randomResultClient = result[randomIndex];
|
||||
|
||||
expect(result.length).toBeGreaterThanOrEqual(20);
|
||||
expect(randomResultClient.postcode).toEqual('46460');
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,21 +1,39 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('Client updatePortfolio', () => {
|
||||
const clientId = 1108;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'},
|
||||
[`__`]: value => {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the portfolioWeight when the salesPerson of a client changes', async() => {
|
||||
const clientId = 1108;
|
||||
const salesPersonId = 18;
|
||||
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const expectedResult = 841.63;
|
||||
|
||||
const clientQuery = `UPDATE vn.client SET salesPersonFk = ${salesPersonId} WHERE id = ${clientId}; `;
|
||||
await models.Client.rawSql(clientQuery);
|
||||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttribute('salesPersonFk', salesPersonId, options);
|
||||
|
||||
await models.Client.updatePortfolio();
|
||||
await models.Client.updatePortfolio(options);
|
||||
|
||||
const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `;
|
||||
const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options);
|
||||
|
@ -30,21 +48,21 @@ describe('Client updatePortfolio', () => {
|
|||
});
|
||||
|
||||
it('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => {
|
||||
pending('task 3817');
|
||||
const clientId = 1107;
|
||||
const salesPersonId = 19;
|
||||
const tx = await models.Client.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const expectedResult = 34.40;
|
||||
|
||||
await models.Client.rawSql(`UPDATE vn.client SET salesPersonFk = NULL WHERE id = ${clientId}; `);
|
||||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttribute('salesPersonFk', null, options);
|
||||
|
||||
await models.Client.updatePortfolio();
|
||||
|
||||
const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `;
|
||||
const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options);
|
||||
const [salesPerson] = await models.Client.rawSql(portfolioQuery);
|
||||
|
||||
expect(salesPerson.portfolioWeight).toEqual(expectedResult);
|
||||
|
||||
|
|
|
@ -125,10 +125,10 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
const isAdministrative = await models.Account.hasRole(userId, 'administrative', myOptions);
|
||||
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
||||
const client = await models.Client.findById(clientId, null, myOptions);
|
||||
|
||||
if (!isAdministrative && client.isTaxDataChecked)
|
||||
if (!isSalesAssistant && client.isTaxDataChecked)
|
||||
throw new UserError(`Not enough privileges to edit a client with verified data`);
|
||||
|
||||
// Sage data validation
|
||||
|
|
|
@ -13,8 +13,13 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.updatePortfolio = async() => {
|
||||
Self.updatePortfolio = async options => {
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
query = `CALL bs.salesPerson_updatePortfolio()`;
|
||||
return await Self.rawSql(query);
|
||||
return Self.rawSql(query, null, myOptions);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -31,6 +31,7 @@ module.exports = Self => {
|
|||
require('../methods/client/createReceipt')(Self);
|
||||
require('../methods/client/updatePortfolio')(Self);
|
||||
require('../methods/client/checkDuplicated')(Self);
|
||||
require('../methods/client/extendedListFilter')(Self);
|
||||
|
||||
// Validations
|
||||
|
||||
|
@ -232,7 +233,6 @@ module.exports = Self => {
|
|||
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||
const userId = loopBackContext.active.accessToken.userId;
|
||||
|
||||
const isAdministrative = await models.Account.hasRole(userId, 'administrative', ctx.options);
|
||||
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', ctx.options);
|
||||
const hasChanges = orgData && changes;
|
||||
|
||||
|
@ -245,7 +245,7 @@ module.exports = Self => {
|
|||
const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk);
|
||||
const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType;
|
||||
|
||||
const cantEditVerifiedData = isTaxDataCheckedChanged && !isAdministrative;
|
||||
const cantEditVerifiedData = isTaxDataCheckedChanged && !isSalesAssistant;
|
||||
const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !isSalesAssistant;
|
||||
|
||||
if (cantEditVerifiedData || cantChangeSageData)
|
||||
|
|
|
@ -136,6 +136,9 @@
|
|||
"mysql": {
|
||||
"columnName": "businessTypeFk"
|
||||
}
|
||||
},
|
||||
"salesPersonFk": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="Clients/extendedListFilter"
|
||||
limit="20">
|
||||
</vn-crud-model>
|
||||
<vn-portal slot="topbar">
|
||||
<vn-searchbar
|
||||
vn-focus
|
||||
panel="vn-client-search-panel"
|
||||
placeholder="Search client"
|
||||
info="Search client by id or name"
|
||||
auto-state="false"
|
||||
model="model">
|
||||
</vn-searchbar>
|
||||
</vn-portal>
|
||||
<vn-card>
|
||||
<smart-table
|
||||
model="model"
|
||||
view-config-id="clientsDetail"
|
||||
options="$ctrl.smartTableOptions"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)">
|
||||
<slot-table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th field="id">
|
||||
<span translate>Identifier</span>
|
||||
</th>
|
||||
<th field="name">
|
||||
<span translate>Name</span>
|
||||
</th>
|
||||
<th field="socialName">
|
||||
<span translate>Social name</span>
|
||||
</th>
|
||||
<th field="fi">
|
||||
<span translate>Tax number</span>
|
||||
</th>
|
||||
<th field="salesPersonFk">
|
||||
<span translate>Salesperson</span>
|
||||
</th>
|
||||
<th field="credit">
|
||||
<span translate>Credit</span>
|
||||
</th>
|
||||
<th field="creditInsurance">
|
||||
<span translate>Credit insurance</span>
|
||||
</th>
|
||||
<th field="phone">
|
||||
<span translate>Phone</span>
|
||||
</th>
|
||||
<th field="mobile">
|
||||
<span translate>Mobile</span>
|
||||
</th>
|
||||
<th field="street">
|
||||
<span translate>Street</span>
|
||||
</th>
|
||||
<th field="countryFk">
|
||||
<span translate>Country</span>
|
||||
</th>
|
||||
<th field="provinceFk">
|
||||
<span translate>Province</span>
|
||||
</th>
|
||||
<th field="city">
|
||||
<span translate>City</span>
|
||||
</th>
|
||||
<th field="postcode">
|
||||
<span translate>Postcode</span>
|
||||
</th>
|
||||
<th field="email">
|
||||
<span translate>Email</span>
|
||||
</th>
|
||||
<th field="created">
|
||||
<span translate>Created</span>
|
||||
</th>
|
||||
<th field="businessTypeFk">
|
||||
<span translate>Business type</span>
|
||||
</th>
|
||||
<th field="payMethodFk">
|
||||
<span translate>Billing data</span>
|
||||
</th>
|
||||
<th field="sageTaxTypeFk">
|
||||
<span translate>Sage tax type</span>
|
||||
</th>
|
||||
<th field="sageTransactionTypeFk">
|
||||
<span translate>Sage tr. type</span>
|
||||
</th>
|
||||
<th field="isActive" centered>
|
||||
<span translate>Active</span>
|
||||
</th>
|
||||
<th field="isVies" centered>
|
||||
<span translate>Vies</span>
|
||||
</th>
|
||||
<th field="isTaxDataChecked" centered>
|
||||
<span translate>Verified data</span>
|
||||
</th>
|
||||
<th field="isEqualizated" centered>
|
||||
<span translate>Is equalizated</span>
|
||||
</th>
|
||||
<th field="isFreezed" centered>
|
||||
<span translate>Freezed</span>
|
||||
</th>
|
||||
<th field="hasToInvoice" centered>
|
||||
<span translate>Invoice</span>
|
||||
</th>
|
||||
<th field="hasToInvoiceByAddress" centered>
|
||||
<span translate>Invoice by address</span>
|
||||
</th>
|
||||
<th field="isToBeMailed" centered>
|
||||
<span translate>Mailing</span>
|
||||
</th>
|
||||
<th field="hasLcr" centered>
|
||||
<span translate>Received LCR</span>
|
||||
</th>
|
||||
<th field="hasCoreVnl" centered>
|
||||
<span translate>Received core VNL</span>
|
||||
</th>
|
||||
<th field="hasSepaVnl" centered>
|
||||
<span translate>Received B2B VNL</span>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="client in model.data"
|
||||
vn-anchor="::{
|
||||
state: 'client.card.summary',
|
||||
params: {id: client.id}
|
||||
}">
|
||||
<td>
|
||||
<vn-icon-button ng-show="::client.isActive == false"
|
||||
vn-tooltip="Client inactive"
|
||||
icon="icon-disabled">
|
||||
</vn-icon-button>
|
||||
<vn-icon-button ng-show="::client.isActive && client.isFreezed == true"
|
||||
vn-tooltip="Client frozen"
|
||||
icon="icon-frozen">
|
||||
</vn-icon-button>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
vn-click-stop="clientDescriptor.show($event, client.id)"
|
||||
class="link">
|
||||
{{::client.id}}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{::client.name}}</td>
|
||||
<td>{{::client.socialName}}</td>
|
||||
<td>{{::client.fi}}</td>
|
||||
<td>
|
||||
<span
|
||||
vn-click-stop="workerDescriptor.show($event, client.salesPersonFk)"
|
||||
ng-class="{'link': client.salesPersonFk}">
|
||||
{{::client.salesPerson | dashIfEmpty}}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{::client.credit}}</td>
|
||||
<td>{{::client.creditInsurance | dashIfEmpty}}</td>
|
||||
<td>{{::client.phone | dashIfEmpty}}</td>
|
||||
<td>{{::client.mobile | dashIfEmpty}}</td>
|
||||
<td>{{::client.street | dashIfEmpty}}</td>
|
||||
<td>{{::client.country | dashIfEmpty}}</td>
|
||||
<td>{{::client.province | dashIfEmpty}}</td>
|
||||
<td>{{::client.city | dashIfEmpty}}</td>
|
||||
<td>{{::client.postcode | dashIfEmpty}}</td>
|
||||
<td>{{::client.email | dashIfEmpty}}</td>
|
||||
<td>{{::client.created | date:'dd/MM/yyyy'}}</td>
|
||||
<td>{{::client.businessType | dashIfEmpty}}</td>
|
||||
<td>{{::client.payMethod | dashIfEmpty}}</td>
|
||||
<td>{{::client.sageTaxType | dashIfEmpty}}</td>
|
||||
<td>{{::client.sageTransactionType | dashIfEmpty}}</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.isActive,
|
||||
'alert': !client.isActive,
|
||||
}">
|
||||
{{ ::client.isActive ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.isVies,
|
||||
'alert': !client.isVies,
|
||||
}">
|
||||
{{ ::client.isVies ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.isTaxDataChecked,
|
||||
'alert': !client.isTaxDataChecked,
|
||||
}">
|
||||
{{ ::client.isTaxDataChecked ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.isEqualizated,
|
||||
'alert': !client.isEqualizated,
|
||||
}">
|
||||
{{ ::client.isEqualizated ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.isFreezed,
|
||||
'alert': !client.isFreezed,
|
||||
}">
|
||||
{{ ::client.isFreezed ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.hasToInvoice,
|
||||
'alert': !client.hasToInvoice,
|
||||
}">
|
||||
{{ ::client.hasToInvoice ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.hasToInvoiceByAddress,
|
||||
'alert': !client.hasToInvoiceByAddress,
|
||||
}">
|
||||
{{ ::client.hasToInvoiceByAddress ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.isToBeMailed,
|
||||
'alert': !client.isToBeMailed,
|
||||
}">
|
||||
{{ ::client.isToBeMailed ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.hasLcr,
|
||||
'alert': !client.hasLcr,
|
||||
}">
|
||||
{{ ::client.hasLcr ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.hasCoreVnl,
|
||||
'alert': !client.hasCoreVnl,
|
||||
}">
|
||||
{{ ::client.hasCoreVnl ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td centered>
|
||||
<vn-chip ng-class="::{
|
||||
'success': client.hasSepaVnl,
|
||||
'alert': !client.hasSepaVnl,
|
||||
}">
|
||||
{{ ::client.hasSepaVnl ? 'Yes' : 'No' | translate}}
|
||||
</vn-chip>
|
||||
</td>
|
||||
<td shrink>
|
||||
<vn-horizontal class="buttons">
|
||||
<vn-icon-button vn-anchor="{state: 'ticket.index', params: {q: {clientFk: client.id} } }"
|
||||
vn-tooltip="Client tickets"
|
||||
icon="icon-ticket">
|
||||
</vn-icon-button>
|
||||
<vn-icon-button
|
||||
vn-click-stop="$ctrl.preview(client)"
|
||||
vn-tooltip="Preview"
|
||||
icon="preview">
|
||||
</vn-icon-button>
|
||||
</vn-horizontal>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</slot-table>
|
||||
</smart-table>
|
||||
</vn-card>
|
||||
<a ui-sref="client.create" vn-tooltip="New client" vn-bind="+" fixed-bottom-right>
|
||||
<vn-float-button icon="add"></vn-float-button>
|
||||
</a>
|
||||
<vn-client-descriptor-popover
|
||||
vn-id="client-descriptor">
|
||||
</vn-client-descriptor-popover>
|
||||
<vn-worker-descriptor-popover
|
||||
vn-id="worker-descriptor">
|
||||
</vn-worker-descriptor-popover>
|
||||
|
||||
<vn-popup vn-id="preview">
|
||||
<vn-client-summary
|
||||
client="$ctrl.clientSelected">
|
||||
</vn-client-summary>
|
||||
</vn-popup>
|
||||
<vn-contextmenu
|
||||
vn-id="contextmenu"
|
||||
targets="['smart-table']"
|
||||
model="model"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)">
|
||||
<slot-menu>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.filterBySelection()">
|
||||
Filter by selection
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.excludeSelection()">
|
||||
Exclude selection
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-if="contextmenu.isFilterAllowed()"
|
||||
ng-click="contextmenu.removeFilter()">
|
||||
Remove filter
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
ng-click="contextmenu.removeAllFilters()">
|
||||
Remove all filters
|
||||
</vn-item>
|
||||
</slot-menu>
|
||||
</vn-contextmenu>
|
|
@ -0,0 +1,184 @@
|
|||
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,
|
||||
shownColumns: true,
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'socialName',
|
||||
autocomplete: {
|
||||
url: 'Clients',
|
||||
showField: 'socialName',
|
||||
valueField: 'socialName',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'created',
|
||||
datepicker: true
|
||||
},
|
||||
{
|
||||
field: 'countryFk',
|
||||
autocomplete: {
|
||||
url: 'Countries',
|
||||
showField: 'country',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'provinceFk',
|
||||
autocomplete: {
|
||||
url: 'Provinces'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'salesPersonFk',
|
||||
autocomplete: {
|
||||
url: 'Workers/activeWithInheritedRole',
|
||||
where: `{role: 'salesPerson'}`,
|
||||
searchFunction: '{firstName: $search}',
|
||||
showField: 'nickname',
|
||||
valueField: 'id',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'businessTypeFk',
|
||||
autocomplete: {
|
||||
url: 'BusinessTypes',
|
||||
valueField: 'code',
|
||||
showField: 'description',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'payMethodFk',
|
||||
autocomplete: {
|
||||
url: 'PayMethods',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'sageTaxTypeFk',
|
||||
autocomplete: {
|
||||
url: 'SageTaxTypes',
|
||||
showField: 'vat',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'sageTransactionTypeFk',
|
||||
autocomplete: {
|
||||
url: 'SageTransactionTypes',
|
||||
showField: 'transaction',
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'isActive',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'isVies',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'isTaxDataChecked',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'isEqualizated',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'isFreezed',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'hasToInvoice',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'hasToInvoiceByAddress',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'isToBeMailed',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'hasSepaVnl',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'hasLcr',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'hasCoreVnl',
|
||||
checkbox: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
exprBuilder(param, value) {
|
||||
switch (param) {
|
||||
case 'created':
|
||||
return {'c.created': {
|
||||
between: this.dateRange(value)}
|
||||
};
|
||||
case 'id':
|
||||
case 'name':
|
||||
case 'socialName':
|
||||
case 'fi':
|
||||
case 'credit':
|
||||
case 'creditInsurance':
|
||||
case 'phone':
|
||||
case 'mobile':
|
||||
case 'street':
|
||||
case 'city':
|
||||
case 'postcode':
|
||||
case 'email':
|
||||
case 'isActive':
|
||||
case 'isVies':
|
||||
case 'isTaxDataChecked':
|
||||
case 'isEqualizated':
|
||||
case 'isFreezed':
|
||||
case 'hasToInvoice':
|
||||
case 'hasToInvoiceByAddress':
|
||||
case 'isToBeMailed':
|
||||
case 'hasSepaVnl':
|
||||
case 'hasLcr':
|
||||
case 'hasCoreVnl':
|
||||
case 'countryFk':
|
||||
case 'provinceFk':
|
||||
case 'salesPersonFk':
|
||||
case 'businessTypeFk':
|
||||
case 'payMethodFk':
|
||||
case 'sageTaxTypeFk':
|
||||
case 'sageTransactionTypeFk':
|
||||
return {[`c.${param}`]: value};
|
||||
}
|
||||
}
|
||||
|
||||
dateRange(value) {
|
||||
const minHour = new Date(value);
|
||||
minHour.setHours(0, 0, 0, 0);
|
||||
const maxHour = new Date(value);
|
||||
maxHour.setHours(23, 59, 59, 59);
|
||||
|
||||
return [minHour, maxHour];
|
||||
}
|
||||
|
||||
preview(client) {
|
||||
this.clientSelected = client;
|
||||
this.$.preview.show();
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnClientExtendedList', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Mailing: Env. emails
|
||||
Sage tr. type: Tipo tr. sage
|
||||
Yes: Sí
|
|
@ -0,0 +1,6 @@
|
|||
@import "variables";
|
||||
|
||||
vn-chip.success,
|
||||
vn-chip.alert {
|
||||
color: $color-font-bg
|
||||
}
|
|
@ -182,7 +182,7 @@
|
|||
vn-one
|
||||
label="Verified data"
|
||||
ng-model="$ctrl.client.isTaxDataChecked"
|
||||
vn-acl="administrative">
|
||||
vn-acl="salesAssistant">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
|
|
|
@ -47,3 +47,4 @@ import './consumption-search-panel';
|
|||
import './defaulter';
|
||||
import './notification';
|
||||
import './unpaid';
|
||||
import './extended-list';
|
||||
|
|
|
@ -33,6 +33,7 @@ Search client by id or name: Buscar clientes por identificador o nombre
|
|||
# Sections
|
||||
|
||||
Clients: Clientes
|
||||
Extended list: Listado extendido
|
||||
Defaulter: Morosos
|
||||
New client: Nuevo cliente
|
||||
Fiscal data: Datos fiscales
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"menus": {
|
||||
"main": [
|
||||
{"state": "client.index", "icon": "person"},
|
||||
{"state": "client.extendedList", "icon": "person"},
|
||||
{"state": "client.notification", "icon": "campaign"},
|
||||
{"state": "client.defaulter", "icon": "icon-defaulter"}
|
||||
],
|
||||
|
@ -381,6 +382,12 @@
|
|||
"component": "vn-client-unpaid",
|
||||
"acl": ["administrative"],
|
||||
"description": "Unpaid"
|
||||
},
|
||||
{
|
||||
"url": "/extended-list",
|
||||
"state": "client.extendedList",
|
||||
"component": "vn-client-extended-list",
|
||||
"description": "Extended list"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Client id: Id cliente
|
||||
Tax number: NIF/CIF
|
||||
Name: Nombre
|
||||
Social name: Razon social
|
||||
Social name: Razón social
|
||||
Town/City: Ciudad
|
||||
Postcode: Código postal
|
||||
Email: E-mail
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('upload', {
|
||||
description: 'Upload and attach a access file',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'appName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The app name'
|
||||
},
|
||||
{
|
||||
arg: 'newVersion',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: `The new version number`
|
||||
},
|
||||
{
|
||||
arg: 'branch',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: `The branch name`
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/upload`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.upload = async(ctx, appName, newVersion, branch, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
|
||||
const TempContainer = models.TempContainer;
|
||||
const AccessContainer = models.AccessContainer;
|
||||
const fileOptions = {};
|
||||
|
||||
let tx;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
let srcFile;
|
||||
try {
|
||||
const tempContainer = await TempContainer.container('access');
|
||||
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const files = Object.values(uploaded.files).map(file => {
|
||||
return file[0];
|
||||
});
|
||||
const uploadedFile = files[0];
|
||||
|
||||
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
|
||||
srcFile = path.join(file.client.root, file.container, file.name);
|
||||
|
||||
const accessContainer = await AccessContainer.container('.archive');
|
||||
const destinationFile = path.join(
|
||||
accessContainer.client.root, accessContainer.name, appName, `${newVersion}.7z`);
|
||||
|
||||
if (process.env.NODE_ENV == 'test')
|
||||
await fs.unlink(srcFile);
|
||||
else {
|
||||
await fs.move(srcFile, destinationFile, {
|
||||
overwrite: true
|
||||
});
|
||||
await fs.chmod(destinationFile, 0o644);
|
||||
|
||||
const existBranch = await models.MdbBranch.findOne({
|
||||
where: {name: branch}
|
||||
});
|
||||
|
||||
if (!existBranch)
|
||||
throw new UserError('Not exist this branch');
|
||||
|
||||
const branchPath = path.join(accessContainer.client.root, 'branches', branch);
|
||||
await fs.mkdir(branchPath, {recursive: true});
|
||||
|
||||
const destinationBranch = path.join(branchPath, `${appName}.7z`);
|
||||
const destinationRoot = path.join(accessContainer.client.root, `${appName}.7z`);
|
||||
try {
|
||||
await fs.unlink(destinationBranch);
|
||||
} catch (e) {}
|
||||
await fs.symlink(destinationFile, destinationBranch);
|
||||
|
||||
if (branch == 'master') {
|
||||
try {
|
||||
await fs.unlink(destinationRoot);
|
||||
} catch (e) {}
|
||||
await fs.symlink(destinationFile, destinationRoot);
|
||||
}
|
||||
}
|
||||
|
||||
await models.MdbVersion.upsert({
|
||||
app: appName,
|
||||
branchFk: branch,
|
||||
version: newVersion
|
||||
});
|
||||
|
||||
if (tx) await tx.commit();
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
|
||||
if (fs.existsSync(srcFile))
|
||||
await fs.unlink(srcFile);
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"MdbBranch": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"MdbVersion": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"AccessContainer": {
|
||||
"dataSource": "accessStorage"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "AccessContainer",
|
||||
"base": "Container",
|
||||
"acls": [{
|
||||
"accessType": "*",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "developer",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "MdbBranch",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "mdbBranch"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"name": {
|
||||
"id": true,
|
||||
"type": "string",
|
||||
"description": "Identifier"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/mdbVersion/upload')(Self);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "MdbVersion",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "mdbVersion"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"app": {
|
||||
"type": "string",
|
||||
"description": "The app name",
|
||||
"id": true
|
||||
},
|
||||
"version": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"branch": {
|
||||
"type": "belongsTo",
|
||||
"model": "MdbBranch",
|
||||
"foreignKey": "branchFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -304,7 +304,8 @@ module.exports = Self => {
|
|||
{'tp.hasTicketRequest': true},
|
||||
{'tp.hasComponentLack': true},
|
||||
{'tp.isTaxDataChecked': false},
|
||||
{'tp.itemShortage': {neq: null}}
|
||||
{'tp.itemShortage': {neq: null}},
|
||||
{'tp.isTooLittle': true}
|
||||
]};
|
||||
} else if (hasProblems === false) {
|
||||
whereProblems = {and: [
|
||||
|
@ -313,7 +314,8 @@ module.exports = Self => {
|
|||
{'tp.hasTicketRequest': false},
|
||||
{'tp.hasComponentLack': false},
|
||||
{'tp.isTaxDataChecked': true},
|
||||
{'tp.itemShortage': null}
|
||||
{'tp.itemShortage': null},
|
||||
{'tp.isTooLittle': false}
|
||||
]};
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ export default class Controller extends Section {
|
|||
},
|
||||
{
|
||||
field: 'shippedDate',
|
||||
searchable: false
|
||||
datepicker: true
|
||||
},
|
||||
{
|
||||
field: 'theoreticalHour',
|
||||
|
|
|
@ -36,7 +36,6 @@ module.exports = Self => {
|
|||
e.workerFk,
|
||||
i1.name packageItemName,
|
||||
e.counter,
|
||||
e.checked,
|
||||
i2.name freightItemName,
|
||||
e.itemFk,
|
||||
u.name userName,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Forces tmp folder creation!
|
Loading…
Reference in New Issue