4975-mdbVersion-last-method #1245

Merged
guillermo merged 12 commits from 4975-mdbVersion-last-method into dev 2023-01-16 11:40:23 +00:00
28 changed files with 466 additions and 153 deletions
Showing only changes of commit 7186b76063 - Show all commits

View File

@ -25,10 +25,10 @@ module.exports = Self => {
return false; return false;
const con = mysql.createConnection({ const con = mysql.createConnection({
host: `${config.hostDb}`, host: config.hostDb,
user: `${config.userDb}`, user: config.userDb,
password: `${config.passwordDb}`, password: config.passwordDb,
port: `${config.portDb}` port: config.portDb
}); });
const sql = `SELECT ot.ticket_id, ot.number const sql = `SELECT ot.ticket_id, ot.number
@ -38,23 +38,23 @@ module.exports = Self => {
JOIN ( JOIN (
SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated
FROM osticket.ost_thread_entry ote FROM osticket.ost_thread_entry ote
WHERE ote.staff_id != 0 AND ote.type = 'R' WHERE ote.staff_id AND ote.type = 'R'
GROUP BY ote.thread_id GROUP BY ote.thread_id
) sub ON sub.thread_id = ot2.id ) sub ON sub.thread_id = ot2.id
WHERE ot.isanswered = 1 WHERE ot.isanswered
AND ots.state = '${config.oldStatus}' AND ots.state = ?
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`; AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`;
let ticketsId = []; const ticketsId = [];
con.connect(err => { con.connect(err => {
if (err) throw err; if (err) throw err;
con.query(sql, (err, results) => { con.query(sql, [config.oldStatus, config.day],
(err, results) => {
if (err) throw err; if (err) throw err;
for (const result of results) for (const result of results)
ticketsId.push(result.ticket_id); ticketsId.push(result.ticket_id);
}); });
}); });
await getRequestToken(); await getRequestToken();
async function getRequestToken() { async function getRequestToken() {
@ -94,30 +94,20 @@ module.exports = Self => {
await close(token, secondCookie); await close(token, secondCookie);
} }
async function getLockCode(token, secondCookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
const response = await fetch(ostUri, params);
const body = await response.text();
const json = JSON.parse(body);
return json.code;
}
async function close(token, secondCookie) { async function close(token, secondCookie) {
for (const ticketId of ticketsId) { for (const ticketId of ticketsId) {
const lockCode = await getLockCode(token, secondCookie, ticketId); try {
const lock = await getLockCode(token, secondCookie, ticketId);
if (!lock.code) {
let error = `Can't get lock code`;
if (lock.msg) error += `: ${lock.msg}`;
throw new Error(error);
}
let form = new FormData(); let form = new FormData();
form.append('__CSRFToken__', token); form.append('__CSRFToken__', token);
form.append('id', ticketId); form.append('id', ticketId);
form.append('a', config.responseType); form.append('a', config.responseType);
form.append('lockCode', lockCode); form.append('lockCode', lock.code);
form.append('from_email_id', config.fromEmailId); form.append('from_email_id', config.fromEmailId);
form.append('reply-to', config.replyTo); form.append('reply-to', config.replyTo);
form.append('cannedResp', 0); form.append('cannedResp', 0);
@ -133,8 +123,29 @@ module.exports = Self => {
'Cookie': secondCookie 'Cookie': secondCookie
} }
}; };
return fetch(ostUri, params); await fetch(ostUri, params);
} catch (e) {
const err = new Error(`${ticketId} Ticket close failed: ${e.message}`);
err.stack += e.stack;
console.error(err);
} }
} }
}
async function getLockCode(token, secondCookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
const response = await fetch(ostUri, params);
const body = await response.text();
const json = JSON.parse(body);
return json;
}
}; };
}; };

View File

@ -1 +0,0 @@
insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated');

View File

@ -0,0 +1,225 @@
DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`;
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
vSerial VARCHAR(255),
vInvoiceDate DATETIME,
vTaxArea VARCHAR(25),
OUT vNewInvoiceId INT)
BEGIN
/**
* Creación de facturas emitidas.
* requiere previamente tabla ticketToInvoice(id).
*
* @param vSerial serie a la cual se hace la factura
* @param vInvoiceDate fecha de la factura
* @param vTaxArea tipo de iva en relacion a la empresa y al cliente
* @param vNewInvoiceId id de la factura que se acaba de generar
* @return vNewInvoiceId
*/
DECLARE vSpainCountryCode INT DEFAULT 1;
DECLARE vIsAnySaleToInvoice BOOL;
DECLARE vIsAnyServiceToInvoice BOOL;
DECLARE vNewRef VARCHAR(255);
DECLARE vWorker INT DEFAULT account.myUser_getId();
DECLARE vCompany INT;
DECLARE vSupplier INT;
DECLARE vClient INT;
DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1;
DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6;
DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2;
DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R';
DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S';
DECLARE vNewInvoiceInId INT;
DECLARE vIsInterCompany BOOL;
SET vInvoiceDate = IFNULL(vInvoiceDate,CURDATE());
SELECT t.clientFk, t.companyFk
INTO vClient, vCompany
FROM ticketToInvoice tt
JOIN ticket t ON t.id = tt.id
LIMIT 1;
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
DELETE ti.*
FROM ticketToInvoice ti
JOIN ticket t ON t.id = ti.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
JOIN supplier su ON su.id = t.companyFk
JOIN client c ON c.id = t.clientFk
LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk
WHERE YEAR(t.shipped) < 2001
OR c.isTaxDataChecked = FALSE
OR t.isDeleted
OR c.hasToInvoice = FALSE
OR itc.id IS NULL;
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100), ts.id
INTO vIsAnySaleToInvoice, vIsAnyServiceToInvoice
FROM ticketToInvoice t
LEFT JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketService ts ON ts.ticketFk = t.id;
IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice)
AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
THEN
-- el trigger añade el siguiente Id_Factura correspondiente a la vSerial
INSERT INTO invoiceOut
(
ref,
serial,
issued,
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
)
SELECT
1,
vSerial,
vInvoiceDate,
vClient,
getDueDate(vInvoiceDate, dueDay),
vCompany,
IF(vSerial = vCorrectingSerial,
vCplusCorrectingInvoiceTypeFk,
IF(vSerial = vSimplifiedSerial,
vCplusSimplifiedInvoiceTypeFk,
vCplusStandardInvoiceTypeFk))
FROM client
WHERE id = vClient;
SET vNewInvoiceId = LAST_INSERT_ID();
SELECT `ref`
INTO vNewRef
FROM invoiceOut
WHERE id = vNewInvoiceId;
UPDATE ticket t
JOIN ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
FROM ticketToInvoice ti
LEFT JOIN ticketState ts ON ti.id = ts.ticket
JOIN state s
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
INSERT INTO vncontrol.inter(state_id,Id_Ticket,Id_Trabajador)
SELECT * FROM tmp.updateInter;
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
UPDATE invoiceOut io
JOIN (
SELECT SUM(amount) AS total
FROM invoiceOutExpence
WHERE invoiceOutFk = vNewInvoiceId
) base
JOIN (
SELECT SUM(vat) AS total
FROM invoiceOutTax
WHERE invoiceOutFk = vNewInvoiceId
) vat
SET io.amount = base.total + vat.total
WHERE io.id = vNewInvoiceId;
DROP TEMPORARY TABLE tmp.updateInter;
SELECT ios.isCEE INTO vIsInterCompany
FROM vn.ticket t
JOIN vn.invoiceOut io ON io.`ref` = t.refFk
JOIN vn.invoiceOutSerial ios ON ios.code = io.serial
WHERE t.refFk = vNewRef
LIMIT 1;
IF (vIsInterCompany) THEN
SELECT vCompany INTO vSupplier;
SELECT id INTO vCompany FROM company WHERE clientFk = vClient;
INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk)
SELECT vSupplier, vNewRef, vInvoiceDate, vCompany;
SET vNewInvoiceInId = LAST_INSERT_ID();
DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
CREATE TEMPORARY TABLE tmp.ticket
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
CALL `ticket_getTax`('NATIONAL');
SET @vTaxableBaseServices := 0.00;
SET @vTaxCodeGeneral := NULL;
INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInId, @vTaxableBaseServices, sub.expenceFk, sub.taxTypeSageFk , sub.transactionTypeSageFk
FROM (
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk, @vTaxCodeGeneral := i.taxClassCodeFk
FROM tmp.ticketServiceTax tst
JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code
WHERE i.isService
HAVING taxableBase
) sub;
INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInId, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, @vTaxableBaseServices, 0) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk
FROM tmp.ticketTax tt
JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code
WHERE !i.isService
GROUP BY tt.pgcFk
HAVING taxableBase
ORDER BY tt.priority;
CALL invoiceInDueDay_calculate(vNewInvoiceInId);
INSERT INTO invoiceInIntrastat (
invoiceInFk,
intrastatFk,
amount,
stems,
countryFk,
net)
SELECT
vNewInvoiceInId invoiceInFk,
i.intrastatFk,
CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal,
CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems,
su.countryFk,
CAST(SUM(IFNULL(i.stems, 1)
* s.quantity
* IF(ic.grams, ic.grams, i.weightByPiece) / 1000) AS DECIMAL(10,2)) netKg
FROM sale s
JOIN ticket t ON s.ticketFk = t.id
JOIN supplier su ON su.id = t.companyFk
JOIN item i ON i.id = s.itemFk
JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk
JOIN intrastat ir ON ir.id = i.intrastatFk
WHERE t.refFk = vNewRef;
DROP TEMPORARY TABLE tmp.ticket;
DROP TEMPORARY TABLE tmp.ticketAmount;
DROP TEMPORARY TABLE tmp.ticketTax;
DROP TEMPORARY TABLE tmp.ticketServiceTax;
END IF;
END IF;
DROP TEMPORARY TABLE `ticketToInvoice`;
END$$
DELIMITER ;

View File

@ -43,7 +43,7 @@ SET t.code = 'claim'
WHERE t.code LIKE 'Claims' ESCAPE '#'; WHERE t.code LIKE 'Claims' ESCAPE '#';
UPDATE salix.module t UPDATE salix.module t
SET t.code = 'user' SET t.code = 'account'
WHERE t.code LIKE 'Users' ESCAPE '#'; WHERE t.code LIKE 'Users' ESCAPE '#';
UPDATE salix.module t UPDATE salix.module t

View File

@ -0,0 +1,28 @@
ALTER TABLE `vn`.`mdbApp` DROP PRIMARY KEY;
ALTER TABLE `vn`.`mdbApp` ADD CONSTRAINT mdbApp_PK PRIMARY KEY (app,baselineBranchFk);
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('com','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('enc','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('ent','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('eti','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('lab','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('tpv','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('com','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('enc','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('ent','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('eti','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('lab','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('tpv','dev');

View File

@ -2692,6 +2692,7 @@ INSERT INTO `util`.`notificationConfig`
INSERT INTO `util`.`notification` (`id`, `name`, `description`) INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES VALUES
(1, 'print-email', 'notification fixture one'), (1, 'print-email', 'notification fixture one'),
(2, 'invoice-electronic', 'A electronic invoice has been generated'),
(4, 'supplier-pay-method-update', 'A supplier pay method has been updated'); (4, 'supplier-pay-method-update', 'A supplier pay method has been updated');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)

View File

@ -15,7 +15,7 @@ describe('SmartTable SearchBar integration', () => {
await browser.close(); await browser.close();
}); });
describe('as filters', () => { describe('as filters in smart-table section', () => {
it('should search by type in searchBar', async() => { it('should search by type in searchBar', async() => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
@ -47,6 +47,34 @@ describe('SmartTable SearchBar integration', () => {
}); });
}); });
describe('as filters in section without smart-table', () => {
it('go to zone section', async() => {
await page.loginAndModule('salesPerson', 'zone');
await page.waitToClick(selectors.globalItems.searchButton);
});
it('should search in searchBar first time', async() => {
await page.doSearch('A');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(7);
});
it('should search in searchBar second time', async() => {
await page.doSearch('A');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(7);
});
it('should search in searchBar third time', async() => {
await page.doSearch('A');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(7);
});
});
describe('as orders', () => { describe('as orders', () => {
it('should order by first id', async() => { it('should order by first id', async() => {
await page.loginAndModule('developer', 'item'); await page.loginAndModule('developer', 'item');

View File

@ -308,7 +308,7 @@ export default class Searchbar extends Component {
this.tableQ = null; this.tableQ = null;
const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length; const hasParams = this.$params.q && JSON.parse(this.$params.q).tableQ;
if (hasParams) { if (hasParams) {
const stateFilter = JSON.parse(this.$params.q); const stateFilter = JSON.parse(this.$params.q);
for (let param in stateFilter) { for (let param in stateFilter) {
@ -325,8 +325,8 @@ export default class Searchbar extends Component {
for (let param in stateFilter.tableQ) for (let param in stateFilter.tableQ)
params[param] = stateFilter.tableQ[param]; params[param] = stateFilter.tableQ[param];
Object.assign(stateFilter, params); const newParams = Object.assign(stateFilter, params);
return this.model.applyParams(params) return this.model.applyParams(newParams)
.then(() => this.model.data); .then(() => this.model.data);
} }

View File

@ -174,14 +174,12 @@ describe('Component vnSearchbar', () => {
jest.spyOn(controller, 'doSearch'); jest.spyOn(controller, 'doSearch');
controller.model = { controller.model = {
refresh: jest.fn(), refresh: jest.fn(),
applyFilter: jest.fn().mockReturnValue(Promise.resolve()),
userParams: { userParams: {
id: 1 id: 1
} }
}; };
controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve());
jest.spyOn(controller.model, 'applyParams');
controller.filter = filter; controller.filter = filter;
controller.removeParam(0); controller.removeParam(0);

View File

@ -1,7 +1,7 @@
Send SMS: Enviar SMS Send SMS: Enviar SMS
Destination: Destinatario Destination: Destinatario
Message: Mensaje Message: Mensaje
SMS sent!: ¡SMS enviado! SMS sent: ¡SMS enviado!
Characters remaining: Carácteres restantes Characters remaining: Carácteres restantes
The destination can't be empty: El destinatario no puede estar vacio The destination can't be empty: El destinatario no puede estar vacio
The message can't be empty: El mensaje no puede estar vacio The message can't be empty: El mensaje no puede estar vacio

View File

@ -65,7 +65,7 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const mdbApp = await models.MdbApp.findById(appName, null, myOptions); const mdbApp = await models.MdbApp.findById(appName, null, myOptions);
if (mdbApp.locked && mdbApp.userFk != userId) { if (mdbApp && mdbApp.locked && mdbApp.userFk != userId) {
throw new UserError($t('App locked', { throw new UserError($t('App locked', {
userId: mdbApp.userFk userId: mdbApp.userFk
})); }));

View File

@ -79,51 +79,51 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="ticket in model.data track by ticket.id" <tr ng-repeat="ticket in model.data track by ticket.id"
vn-anchor="::{ vn-anchor="{
state: 'ticket.card.summary', state: 'ticket.card.summary',
params: {id: ticket.id}, params: {id: ticket.id},
target: '_blank' target: '_blank'
}"> }">
<td> <td>
<vn-icon <vn-icon
ng-show="::ticket.isTaxDataChecked === 0" ng-show="ticket.isTaxDataChecked === 0"
translate-attr="{title: 'No verified data'}" translate-attr="{title: 'No verified data'}"
class="bright" class="bright"
icon="icon-no036"> icon="icon-no036">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.hasTicketRequest" ng-show="ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}" translate-attr="{title: 'Purchase request'}"
class="bright" class="bright"
icon="icon-buyrequest"> icon="icon-buyrequest">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.itemShortage" ng-show="ticket.itemShortage"
translate-attr="{title: 'Not visible'}" translate-attr="{title: 'Not visible'}"
class="bright" class="bright"
icon="icon-unavailable"> icon="icon-unavailable">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.isFreezed" ng-show="ticket.isFreezed"
translate-attr="{title: 'Client frozen'}" translate-attr="{title: 'Client frozen'}"
class="bright" class="bright"
icon="icon-frozen"> icon="icon-frozen">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.risk" ng-show="ticket.risk"
ng-class="::{'highRisk': ticket.hasHighRisk}" ng-class="{'highRisk': ticket.hasHighRisk}"
title="{{::$ctrl.$t('Risk')}}: {{ticket.risk}}" title="{{$ctrl.$t('Risk')}}: {{ticket.risk}}"
class="bright" class="bright"
icon="icon-risk"> icon="icon-risk">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.hasComponentLack" ng-show="ticket.hasComponentLack"
translate-attr="{title: 'Component lack'}" translate-attr="{title: 'Component lack'}"
class="bright" class="bright"
icon="icon-components"> icon="icon-components">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.isTooLittle" ng-show="ticket.isTooLittle"
translate-attr="{title: 'Ticket too little'}" translate-attr="{title: 'Ticket too little'}"
class="bright" class="bright"
icon="icon-isTooLittle"> icon="icon-isTooLittle">
@ -133,64 +133,64 @@
<span <span
vn-click-stop="ticketDescriptor.show($event, ticket.id)" vn-click-stop="ticketDescriptor.show($event, ticket.id)"
class="link"> class="link">
{{::ticket.id}} {{ticket.id}}
</span> </span>
</td> </td>
<td name="nickname"> <td name="nickname">
<span <span
title="{{::ticket.nickname}}" title="{{ticket.nickname}}"
vn-click-stop="clientDescriptor.show($event, ticket.clientFk)" vn-click-stop="clientDescriptor.show($event, ticket.clientFk)"
class="link"> class="link">
{{::ticket.nickname}} {{ticket.nickname}}
</span> </span>
</td> </td>
<td> <td>
<span <span
title="{{::ticket.userName}}" title="{{ticket.userName}}"
vn-click-stop="workerDescriptor.show($event, ticket.salesPersonFk)" vn-click-stop="workerDescriptor.show($event, ticket.salesPersonFk)"
class="link"> class="link">
{{::ticket.userName | dashIfEmpty}} {{ticket.userName | dashIfEmpty}}
</span> </span>
</td> </td>
<td> <td>
<span class="chip {{::$ctrl.compareDate(ticket.shippedDate)}}"> <span class="chip {{$ctrl.compareDate(ticket.shippedDate)}}">
{{::ticket.shippedDate | date: 'dd/MM/yyyy'}} {{ticket.shippedDate | date: 'dd/MM/yyyy'}}
</span> </span>
</td> </td>
<td>{{::ticket.zoneLanding | date: 'HH:mm'}}</td> <td>{{ticket.zoneLanding | date: 'HH:mm'}}</td>
<td>{{::ticket.practicalHour | date: 'HH:mm'}}</td> <td>{{ticket.practicalHour | date: 'HH:mm'}}</td>
<td>{{::ticket.shipped | date: 'HH:mm'}}</td> <td>{{ticket.shipped | date: 'HH:mm'}}</td>
<td>{{::ticket.province}}</td> <td>{{ticket.province}}</td>
<td> <td>
<span <span
ng-show="::ticket.refFk" ng-show="ticket.refFk"
title="{{::ticket.refFk}}" title="{{ticket.refFk}}"
vn-click-stop="invoiceOutDescriptor.show($event, ticket.invoiceOutId)" vn-click-stop="invoiceOutDescriptor.show($event, ticket.invoiceOutId)"
class="link"> class="link">
{{::ticket.refFk}} {{ticket.refFk}}
</span> </span>
<span <span
ng-show="::!ticket.refFk" ng-show="!ticket.refFk"
class="chip {{::ticket.classColor}}"> class="chip {{ticket.classColor}}">
{{::ticket.state}} {{ticket.state}}
</span> </span>
</td> </td>
<td name="zone"> <td name="zone">
<span <span
title="{{::ticket.zoneName}}" title="{{ticket.zoneName}}"
vn-click-stop="zoneDescriptor.show($event, ticket.zoneFk)" vn-click-stop="zoneDescriptor.show($event, ticket.zoneFk)"
class="link"> class="link">
{{::ticket.zoneName | dashIfEmpty}} {{ticket.zoneName | dashIfEmpty}}
</span> </span>
</td> </td>
<td number> <td number>
<span class="chip {{::$ctrl.totalPriceColor(ticket)}}"> <span class="chip {{$ctrl.totalPriceColor(ticket)}}">
{{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}} {{(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
</span> </span>
</td> </td>
<td actions> <td actions>
<vn-icon-button <vn-icon-button
vn-anchor="::{ vn-anchor="{
state: 'ticket.card.sale', state: 'ticket.card.sale',
params: {id: ticket.id}, params: {id: ticket.id},
target: '_blank' target: '_blank'

View File

@ -26,7 +26,7 @@ class Controller extends Component {
throw new Error(`The message it's too long`); throw new Error(`The message it's too long`);
this.$http.post(`Routes/sendSms`, this.sms).then(res => { this.$http.post(`Routes/sendSms`, this.sms).then(res => {
this.vnApp.showMessage(this.$t('SMS sent!')); this.vnApp.showMessage(this.$t('SMS sent'));
if (res.data) this.emit('send', {response: res.data}); if (res.data) this.emit('send', {response: res.data});
}); });

View File

@ -30,7 +30,7 @@ describe('Route', () => {
controller.onResponse(); controller.onResponse();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent!'); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent');
}); });
it('should call onResponse without the destination and show an error snackbar', () => { it('should call onResponse without the destination and show an error snackbar', () => {

View File

@ -24,6 +24,8 @@ module.exports = Self => {
const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions);
const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions);
const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions); const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions);
if (!workerDepartment) return false;
const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); const usesMana = departments.find(department => department.id == workerDepartment.departmentFk);
return usesMana ? true : false; return usesMana ? true : false;

View File

@ -267,8 +267,15 @@ class Controller extends Section {
if (client.hasElectronicInvoice) { if (client.hasElectronicInvoice) {
this.$http.post(`NotificationQueues`, { this.$http.post(`NotificationQueues`, {
notificationFk: 'invoiceElectronic', notificationFk: 'invoice-electronic',
authorFk: client.id, authorFk: client.id,
params: JSON.stringify(
{
'name': client.name,
'email': client.email,
'ticketId': this.id,
'url': window.location.href
})
}).then(() => { }).then(() => {
this.vnApp.showSuccess(this.$t('Invoice sent')); this.vnApp.showSuccess(this.$t('Invoice sent'));
}); });

View File

@ -441,10 +441,11 @@
</vn-popover> </vn-popover>
<!-- SMS Dialog --> <!-- SMS Dialog -->
<vn-ticket-sms <vn-sms-dialog
vn-id="sms" vn-id="sms"
sms="$ctrl.newSMS"> sms="$ctrl.newSMS"
</vn-ticket-sms> on-send="$ctrl.onSmsSend($sms)">
</vn-sms-dialog>
<vn-confirm <vn-confirm
vn-id="delete-lines" vn-id="delete-lines"

View File

@ -389,6 +389,11 @@ class Controller extends Section {
this.$.sms.open(); this.$.sms.open();
} }
onSmsSend(sms) {
return this.$http.post(`Tickets/${this.ticket.id}/sendSms`, sms)
.then(() => this.vnApp.showSuccess(this.$t('SMS sent')));
}
/** /**
* Inserts a new instance * Inserts a new instance
*/ */

View File

@ -27,7 +27,7 @@
<section> <section>
<vn-tool-bar class="vn-mb-md"> <vn-tool-bar class="vn-mb-md">
<vn-button <vn-button
disabled="!$ctrl.hasDateRange" disabled="!travels.length"
icon="picture_as_pdf" icon="picture_as_pdf"
ng-click="$ctrl.showReport()" ng-click="$ctrl.showReport()"
vn-tooltip="Open as PDF"> vn-tooltip="Open as PDF">

View File

@ -43,16 +43,6 @@ class Controller extends Section {
this.smartTableOptions = {}; this.smartTableOptions = {};
} }
get hasDateRange() {
const userParams = this.$.model.userParams;
const hasLanded = userParams.landedTo;
const hasShipped = userParams.shippedFrom;
const hasContinent = userParams.continent;
const hasWarehouseOut = userParams.warehouseOutFk;
return hasLanded || hasShipped || hasContinent || hasWarehouseOut;
}
onDragInterval() { onDragInterval() {
if (this.dragClientY > 0 && this.dragClientY < 75) if (this.dragClientY > 0 && this.dragClientY < 75)
this.$window.scrollTo(0, this.$window.scrollY - 10); this.$window.scrollTo(0, this.$window.scrollY - 10);

View File

@ -14,17 +14,6 @@ describe('Travel Component vnTravelExtraCommunity', () => {
controller.$.model.refresh = jest.fn(); controller.$.model.refresh = jest.fn();
})); }));
describe('hasDateRange()', () => {
it('should return truthy when shippedFrom or landedTo are set as userParams', () => {
const now = new Date();
controller.$.model.userParams = {shippedFrom: now, landedTo: now};
const result = controller.hasDateRange;
expect(result).toBeTruthy();
});
});
describe('findDraggable()', () => { describe('findDraggable()', () => {
it('should find the draggable element', () => { it('should find the draggable element', () => {
const draggable = document.createElement('tr'); const draggable = document.createElement('tr');

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html v-bind="$props">
<head>
<meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no">
<title>{{ $t('subject') }}</title>
</head>
<body>
<h1>{{ $t('title') }} {{name}}</h1>
<p>{{ $t('clientMail') }} {{email}}</p>
<p>{{ $t('ticketId') }} <a :href='url'>{{ticketId}}</a>
</body>
</html>

View File

@ -0,0 +1,21 @@
module.exports = {
name: 'invoice-electronic',
props: {
name: {
type: [String],
required: true
},
email: {
type: [String],
required: true
},
ticketId: {
type: [Number],
required: true
},
url: {
type: [String],
required: true
}
},
};

View File

@ -0,0 +1,4 @@
subject: A electronic invoice has been created
title: A new electronic invoice has been created for the client
clientMail: The client's email is
ticketId: The invoice's ticket is

View File

@ -0,0 +1,4 @@
subject: Se ha creado una factura electrónica
title: Se ha creado una nueva factura electrónica para el cliente
clientMail: El correo del cliente es
ticketId: El ticket de la factura es

View File

@ -82,7 +82,7 @@ module.exports = {
return this.rawSqlFromDef(`taxes`, [reference]); return this.rawSqlFromDef(`taxes`, [reference]);
}, },
fetchIntrastat(reference) { fetchIntrastat(reference) {
return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference, reference]); return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]);
}, },
fetchRectified(reference) { fetchRectified(reference) {
return this.rawSqlFromDef(`rectified`, [reference]); return this.rawSqlFromDef(`rectified`, [reference]);

View File

@ -1,39 +1,26 @@
SELECT * SELECT *
FROM invoiceOut io FROM invoiceOut io
JOIN invoiceOutSerial ios ON io.serial = ios.code JOIN invoiceOutSerial ios ON io.serial = ios.code
JOIN JOIN(
(SELECT SELECT ir.id code,
t.refFk, ir.description,
ir.id code, iii.stems,
ir.description description, iii.net netKg,
CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, iii.amount subtotal
CAST(SUM(CAST(IFNULL(i.stems, 1) * s.quantity * IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) / 1000 AS DECIMAL(10,2)) * FROM vn.invoiceInIntrastat iii
IF(sub.weight, sub.weight / vn.invoiceOut_getWeight(?), 1)) AS DECIMAL(10,2)) netKg, LEFT JOIN vn.invoiceIn ii ON ii.id = iii.invoiceInFk
CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal LEFT JOIN vn.invoiceOut io ON io.ref = ii.supplierRef
FROM vn.ticket t LEFT JOIN vn.intrastat ir ON ir.id = iii.intrastatFk
JOIN vn.sale s ON s.ticketFk = t.id WHERE io.`ref` = ?
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk
JOIN vn.intrastat ir ON ir.id = i.intrastatFk
LEFT JOIN (
SELECT t2.weight
FROM vn.ticket t2
WHERE refFk = ? AND weight
LIMIT 1
) sub ON TRUE
WHERE t.refFk = ?
AND i.intrastatFk
GROUP BY i.intrastatFk
UNION ALL UNION ALL
SELECT SELECT NULL code,
NULL AS refFk, 'Servicios' description,
NULL AS code, 0 stems,
NULL AS description, 0 netKg,
0 AS stems, IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) subtotal
0 AS netKg,
IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) AS subtotal
FROM vn.ticketService ts FROM vn.ticketService ts
JOIN vn.ticket t ON ts.ticketFk = t.id JOIN vn.ticket t ON ts.ticketFk = t.id
WHERE t.refFk = ?) sub WHERE t.refFk = ?
WHERE io.`ref` = ? AND ios.isCEE ) sub
WHERE io.ref = ? AND ios.isCEE
ORDER BY sub.code; ORDER BY sub.code;