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

This commit is contained in:
Joan Sanchez 2020-09-09 08:22:45 +00:00
commit 88cf2e602c
75 changed files with 1956 additions and 164 deletions

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES ('Buy', '*', '*', 'ALLOW', 'ROLE', 'buyer');

View File

@ -1,2 +1,2 @@
ALTER TABLE `vn`.`accountingType`
ADD COLUMN `receiptDescription` VARCHAR(50) NULL AFTER `description`;
ADD COLUMN `receiptDescription` VARCHAR(50) NULL COMMENT 'Descripción por defecto al crear nuevo recibo' AFTER `description`;

View File

@ -0,0 +1,119 @@
USE `vn`;
DROP procedure IF EXISTS `ticket_close`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticket_close`(vTicketFk INT)
BEGIN
/**
* Realiza el cierre de todos los
* tickets de la tabla ticketClosure.
*
* @param vTicketFk Id del ticket
*/
DECLARE vDone BOOL;
DECLARE vClientFk INT;
DECLARE vCurTicketFk INT;
DECLARE vIsTaxDataChecked BOOL;
DECLARE vCompanyFk INT;
DECLARE vShipped DATE;
DECLARE vNewInvoiceId INT;
DECLARE vHasDailyInvoice BOOL;
DECLARE vWithPackage BOOL;
DECLARE vHasToInvoice BOOL;
DECLARE cur CURSOR FOR
SELECT ticketFk FROM tmp.ticketClosure;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
RESIGNAL;
END;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure;
CREATE TEMPORARY TABLE tmp.ticketClosure
SELECT vTicketFk AS ticketFk;
INSERT INTO tmp.ticketClosure
SELECT id FROM stowaway s
WHERE s.shipFk = vTicketFk;
OPEN cur;
proc: LOOP
SET vDone = FALSE;
FETCH cur INTO vCurTicketFk;
IF vDone THEN
LEAVE proc;
END IF;
-- ticketClosure start
SELECT
c.id,
c.isTaxDataChecked,
t.companyFk,
t.shipped,
co.hasDailyInvoice,
w.isManaged,
c.hasToInvoice
INTO vClientFk,
vIsTaxDataChecked,
vCompanyFk,
vShipped,
vHasDailyInvoice,
vWithPackage,
vHasToInvoice
FROM ticket t
JOIN `client` c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
JOIN warehouse w ON w.id = t.warehouseFk
WHERE t.id = vCurTicketFk;
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
(SELECT vCurTicketFk, p.id, COUNT(*)
FROM expedition e
JOIN packaging p ON p.itemFk = e.itemFk
WHERE e.ticketFk = vCurTicketFk AND p.isPackageReturnable
AND vWithPackage
GROUP BY p.itemFk);
-- No retornables o no catalogados
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
(SELECT e.itemFk, vCurTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
FROM expedition e
JOIN item i ON i.id = e.itemFk
LEFT JOIN packaging p ON p.itemFk = i.id
WHERE e.ticketFk = vCurTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
AND getSpecialPrice(e.itemFk, vClientFk) > 0
GROUP BY e.itemFk);
CALL vn.zonePromo_Make();
IF(vHasDailyInvoice) AND vHasToInvoice THEN
-- Facturacion rapida
CALL ticketTrackingAdd(vCurTicketFk, 'DELIVERED', NULL);
-- Facturar si está contabilizado
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
vShipped,
vCompanyFk,
NULL,
vNewInvoiceId);
END IF;
ELSE
CALL ticketTrackingAdd(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk)), NULL);
END IF;
END LOOP;
CLOSE cur;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure;
END$$
DELIMITER ;

View File

@ -733,6 +733,39 @@ INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
(05080000, 'Coral y materiales similares', 2, 2),
(06021010, 'Plantas vivas: Esqueje/injerto, Vid', 1, 1);
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`)
VALUES
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL, 0),
(2, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL, 0),
(3, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL, 0),
(4, 1, 60, 'AMR', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL, 0),
(5, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL, 0),
(6, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL, 0),
(7, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL, 0),
(8, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL, 0),
(9, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL, 0),
(10, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL, 0),
(11, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL, 0),
(12, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL, 0),
(13, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL, 0),
(14, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL, 0),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL, 0),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL, 0),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL, 0);
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `checked`, `workerFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 18),
(3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 3, 1, 18),
(4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 4, 1, 18),
(5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 1, 1, 18),
(7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), 1, 1, 1, 18),
(8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), 1, 1, 1, 18),
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(10, 7, 7, 71, CURDATE(), 1, 1, 1, 18);
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`)
VALUES
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL),
@ -1233,23 +1266,23 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWa
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
VALUES
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE());
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 1, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, NULL, 0, 1, 0, 2.5, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 2.5, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 2.5, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 4, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 4, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, NULL, 0, 1, 0, 4, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE());
INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`,`total`, `date_make`, `first_row_stamp`, `confirm_date`)
VALUES

View File

@ -220,23 +220,23 @@ export default {
topbarSearch: 'vn-topbar',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
closeItemSummaryPreview: '.vn-popup.shown',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="menu"]',
fieldsToShowForm: '.vn-dialog.shown form',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="more_vert"]',
fieldsToShowForm: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index vn-table a:nth-child(1) vn-td:nth-child(1)',
firstItemId: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(2)',
idCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(2) > vn-check',
stemsCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(3) > vn-check',
sizeCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(4) > vn-check',
nicheCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(5) > vn-check',
typeCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(6) > vn-check',
categoryCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(7) > vn-check',
intrastadCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(8) > vn-check',
originCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(9) > vn-check',
buyerCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(10) > vn-check',
destinyCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(11) > vn-check',
taxClassCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(12) > vn-check',
saveFieldsButton: '.vn-dialog.shown vn-horizontal:nth-child(16) > vn-button > button'
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(1) > vn-check',
stemsCheckbox: '.vn-popover.shown vn-horizontal:nth-child(2) > vn-check',
sizeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check',
nicheCheckbox: '.vn-popover.shown vn-horizontal:nth-child(4) > vn-check',
typeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(5) > vn-check',
categoryCheckbox: '.vn-popover.shown vn-horizontal:nth-child(6) > vn-check',
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(7) > vn-check',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(8) > vn-check',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(9) > vn-check',
destinyCheckbox: '.vn-popover.shown vn-horizontal:nth-child(10) > vn-check',
taxClassCheckbox: '.vn-popover.shown vn-horizontal:nth-child(11) > vn-check',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
},
itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
@ -372,6 +372,8 @@ export default {
ticketsIndex: {
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"]',
advancedSearchDaysOnward: 'vn-ticket-search-panel vn-input-number[ng-model="filter.scopeDays"]',
advancedSearchButton: 'vn-ticket-search-panel button[type=submit]',
newTicketButton: 'vn-ticket-index a[ui-sref="ticket.create"]',
searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
secondTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(2) > vn-td:nth-child(1) > vn-check',
@ -384,7 +386,6 @@ export default {
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-summary [label=Landed] span',
topbarSearch: 'vn-searchbar',
advancedSearchButton: 'vn-ticket-search-panel button[type=submit]',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]',
sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6)',
@ -898,5 +899,22 @@ export default {
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(1) span',
travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a',
entriesQuicklink: 'vn-entry-descriptor vn-quick-link[icon="icon-entry"] > a'
},
entryLatestBuys: {
firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)',
allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys a:nth-child(2) vn-check[ng-model="buy.checked"]',
editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]',
fieldAutocomplete: 'vn-autocomplete[ng-model="$ctrl.editedColumn.field"]',
newValueInput: 'vn-textfield[ng-model="$ctrl.editedColumn.newValue"]',
latestBuysSectionButton: 'a[ui-sref="entry.latestBuys"]',
acceptEditBuysDialog: 'button[response="accept"]'
},
entryIndex: {
createEntryButton: 'vn-entry-index vn-button[icon="add"]',
newEntrySupplier: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]',
newEntryTravel: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.travelFk"]',
newEntryCompany: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
saveNewEntry: 'vn-entry-create button[type="submit"]'
}
};

View File

@ -17,6 +17,7 @@ describe('Client Add address path', () => {
});
it(`should click on the add new address button to access to the new address form`, async() => {
await page.waitFor(500);
await page.waitToClick(selectors.clientAddresses.createAddress);
await page.waitForState('client.card.address.create');
});

View File

@ -55,7 +55,7 @@ describe('Item index path', () => {
});
it('should mark all unchecked boxes to leave the index as it was', async() => {
await page.waitFor(3000); // otherwise the snackbar doesnt appear some times.
await page.waitFor(500); // otherwise the snackbar doesnt appear some times.
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
await page.waitToClick(selectors.itemsIndex.idCheckbox);
await page.waitToClick(selectors.itemsIndex.stemsCheckbox);

View File

@ -18,7 +18,7 @@ describe('Ticket purchase request path', () => {
});
it('should add a new request', async() => {
await page.waitFor('.vn-popup', {hidden: true}),
await page.waitFor(500);
await page.waitToClick(selectors.ticketRequests.addRequestButton);
await page.write(selectors.ticketRequests.descriptionInput, 'New stuff');
await page.write(selectors.ticketRequests.quantity, '9');

View File

@ -17,6 +17,7 @@ describe('Route create path', () => {
describe('as employee', () => {
it('should click on the add new route button and open the creation form', async() => {
await page.waitFor(500);
await page.waitToClick(selectors.routeIndex.addNewRouteButton);
await page.waitForState('route.create');
});

View File

@ -18,6 +18,7 @@ describe('InvoiceOut descriptor path', () => {
describe('as Administrative', () => {
it('should search for tickets with an specific invoiceOut', async() => {
await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton);
await page.clearInput(selectors.ticketsIndex.advancedSearchDaysOnward);
await page.write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222');
await page.waitToClick(selectors.ticketsIndex.advancedSearchButton);
await page.waitForState('ticket.card.summary');
@ -63,7 +64,7 @@ describe('InvoiceOut descriptor path', () => {
await page.waitForState('ticket.index');
});
it('should search for tickets with an specific invoiceOut to find no results', async() => {
it('should search now for tickets with an specific invoiceOut to find no results', async() => {
await page.doSearch('T2222222');
const nResults = await page.countElement(selectors.ticketsIndex.searchResult);

View File

@ -0,0 +1,46 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry lastest buys path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
});
afterAll(async() => {
await browser.close();
});
it('should access the latest buys seccion and search not seeing the edit buys button yet', async() => {
await page.waitToClick(selectors.entryLatestBuys.latestBuysSectionButton);
await page.waitFor(250);
await page.keyboard.press('Enter');
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: false});
});
it('should select all lines but one and then check the edit buys button appears', async() => {
await page.waitToClick(selectors.entryLatestBuys.allBuysCheckBox);
await page.waitToClick(selectors.entryLatestBuys.secondBuyCheckBox);
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: true});
});
it('should open the edit dialog', async() => {
await page.waitToClick(selectors.entryLatestBuys.editBuysButton);
await page.waitForSelector(selectors.entryLatestBuys.fieldAutocomplete, {visible: true});
});
it('should search for the "Description" field and type a new description for the items in each selected buy', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.fieldAutocomplete, 'Description');
await page.write(selectors.entryLatestBuys.newValueInput, 'Crafted item');
await page.waitToClick(selectors.entryLatestBuys.acceptEditBuysDialog);
});
it('should navigate to the entry.buy section by clicking one of the buys', async() => {
await page.waitToClick(selectors.entryLatestBuys.firstBuy);
await page.waitForState('entry.card.buy');
});
});

View File

@ -0,0 +1,33 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry create path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
});
afterAll(async() => {
await browser.close();
});
it('should click the create entry button to open the form', async() => {
await page.waitToClick(selectors.entryIndex.createEntryButton);
await page.waitForState('entry.create');
});
it('should fill the form to create a valid entry', async() => {
await page.autocompleteSearch(selectors.entryIndex.newEntrySupplier, '2');
await page.autocompleteSearch(selectors.entryIndex.newEntryTravel, 'Warehouse Three');
await page.autocompleteSearch(selectors.entryIndex.newEntryCompany, 'ORN');
await page.waitToClick(selectors.entryIndex.saveNewEntry);
});
it('should be redirected to entry basic data', async() => {
await page.waitForState('entry.card.basicData');
});
});

View File

@ -7,7 +7,6 @@ export default class Table {
this.table = $element[0];
this.field = null;
this.order = null;
this.autoLoad = true;
}
setOrder(field, order) {
@ -25,9 +24,7 @@ export default class Table {
}
$onChanges() {
// FIXME: The autoload property should be removed from vnTable
// because it's already implemented at vnModel
if (this.autoLoad && this.model && !this.model.data)
if (this.model && !this.model.data)
this.applyOrder();
}

View File

@ -11,7 +11,7 @@ import './visible-by';
import './bind';
import './repeat-last';
import './title';
import './uvc';
import './smart-table';
import './droppable';
import './http-click';
import './http-submit';

View File

@ -1,12 +1,13 @@
<div style="position: relative;">
<div style="position: absolute; top: 0; left: 0; padding: .3em; z-index: 1">
<div style="position: absolute; top: 0; left: 0; z-index: 1">
<vn-icon-button
icon="menu"
ng-click="$ctrl.showUvc($event)">
class="vn-pt-sm"
icon="more_vert"
ng-click="$ctrl.showSmartTable($event)">
</vn-icon-button>
<vn-dialog
<vn-popover
class="modal-form"
vn-id="uvc"
vn-id="smart-table"
message="Fields to show">
<tpl-body>
<div class="vn-pa-md">
@ -24,6 +25,6 @@
</vn-horizontal>
</div>
</tpl-body>
</vn-dialog>
</vn-popover>
</div>
</div>

View File

@ -1,23 +1,28 @@
import ngModule from '../module';
import template from './uvc.html';
import './uvc.scss';
import template from './smart-table.html';
import './smart-table.scss';
/**
* Directive to hide/show selected columns of a table, don't use with rowspan.
* Property smart-table-ignore ignores one or more vn-th with prop field.
*/
directive.$inject = ['$http', '$compile', 'vnApp', '$translate'];
export function directive($http, $compile, vnApp, $translate) {
function getHeaderList($element, $scope) {
let allHeaders = $element[0].querySelectorAll(`vn-th[field], vn-th[th-id]`);
let headerList = Array.from(allHeaders);
let filtrableHeaders = $element[0].querySelectorAll('vn-th[field]:not([smart-table-ignore])');
let headerList = Array.from(filtrableHeaders);
let ids = [];
let titles = {};
headerList.forEach(header => {
let id = header.getAttribute('th-id') || header.getAttribute('field');
let id = header.getAttribute('field');
ids.push(id);
titles[id] = header.innerText || id.charAt(0).toUpperCase() + id.slice(1);
});
$scope.fields = ids;
$scope.titles = titles;
$scope.allHeaders = Array.from($element[0].querySelectorAll('vn-th'));
return headerList;
}
@ -38,34 +43,33 @@ export function directive($http, $compile, vnApp, $translate) {
Object.keys(userConfig.configuration).forEach(key => {
let index;
if (userConfig.configuration[key] === false) {
index = headerList.findIndex(el => {
return (el.getAttribute('th-id') == key || el.getAttribute('field') == key);
index = $scope.allHeaders.findIndex(el => {
return el.getAttribute('field') == key;
});
let baseSelector = `vn-table[vn-uvc=${userConfig.tableCode}] > div`;
let baseSelector = `vn-table[vn-smart-table=${userConfig.tableCode}] > div`;
selectors.push(`${baseSelector} vn-thead > vn-tr > vn-th:nth-child(${index + 1})`);
selectors.push(`${baseSelector} vn-tbody > vn-tr > vn-td:nth-child(${index + 1})`);
selectors.push(`${baseSelector} vn-tbody > .vn-tr > vn-td:nth-child(${index + 1})`);
}
});
if (document.querySelector('style[id="uvc"]')) {
let child = document.querySelector('style[id="uvc"]');
if (document.querySelector('style[id="smart-table"]')) {
let child = document.querySelector('style[id="smart-table"]');
child.parentNode.removeChild(child);
}
if (selectors.length) {
let rule = selectors.join(', ') + '{display: none;}';
$scope.css = document.createElement('style');
$scope.css.setAttribute('id', 'uvc');
$scope.css.setAttribute('id', 'smart-table');
document.head.appendChild($scope.css);
$scope.css.appendChild(document.createTextNode(rule));
}
$scope.$on('$destroy', () => {
if ($scope.css && document.querySelector('style[id="uvc"]')) {
let child = document.querySelector('style[id="uvc"]');
if ($scope.css && document.querySelector('style[id="smart-table"]')) {
let child = document.querySelector('style[id="smart-table"]');
child.parentNode.removeChild(child);
}
});
@ -80,13 +84,13 @@ export function directive($http, $compile, vnApp, $translate) {
restrict: 'A',
link: async function($scope, $element, $attrs) {
let cTemplate = $compile(template)($scope)[0];
$scope.$ctrl.showUvc = event => {
$scope.$ctrl.showSmartTable = event => {
event.preventDefault();
event.stopImmediatePropagation();
$scope.uvc.show();
$scope.smartTable.show(event.target);
};
let tableCode = $attrs.vnUvc.trim();
let tableCode = $attrs.vnSmartTable.trim();
let headerList = getHeaderList($element, $scope);
let defaultConfigView = {tableCode: tableCode, configuration: {}};
@ -98,16 +102,16 @@ export function directive($http, $compile, vnApp, $translate) {
addHideClass($scope, headerList, config);
let table = document.querySelector(`vn-table[vn-uvc=${tableCode}]`);
let table = document.querySelector(`vn-table[vn-smart-table=${tableCode}]`);
table.insertBefore(cTemplate, table.firstChild);
$scope.$ctrl.saveConfiguration = async tableConfiguration => {
let newConfig = await saveConfiguration(tableConfiguration);
vnApp.showSuccess($translate.instant('Data saved!'));
addHideClass($scope, headerList, newConfig.data);
$scope.uvc.hide();
$scope.smartTable.hide();
};
}
};
}
ngModule.directive('vnUvc', directive);
ngModule.directive('vnSmartTable', directive);

View File

@ -1,4 +1,4 @@
vn-table vn-dialog[vn-id="uvc"]{
vn-table vn-popover[vn-id="smart-table"]{
& > div {
min-width: 288px;
align-items: center;

View File

@ -22,7 +22,9 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-latestBuys:before {
content: "\e95f";
}
.icon-zone:before {
content: "\e95d";
}

View File

@ -102,6 +102,7 @@
<glyph unicode="&#xe95c;" glyph-name="anonymous" d="M230.4 605.867c12.8 46.933 29.867 93.867 46.933 140.8 8.533 34.133 21.333 64 29.867 98.133 4.267 12.8 8.533 12.8 21.333 12.8 38.4-12.8 72.533-21.333 110.933-25.6 12.8-4.267 29.867 0 46.933 0 34.133 4.267 68.267 8.533 102.4 12.8s72.533 12.8 106.667 17.067c4.267 0 8.533 0 12.8 0s8.533 0 12.8-8.533c12.8-46.933 29.867-93.867 42.667-136.533 12.8-38.4 25.6-76.8 34.133-115.2-192 4.267-379.733 4.267-567.467 4.267zM456.533 247.467c34.133 8.533 64 12.8 98.133 4.267 8.533 0 12.8 0 12.8 8.533 17.067 34.133 42.667 59.733 76.8 72.533 38.4 17.067 76.8 21.333 115.2 8.533 34.133-8.533 59.733-29.867 81.067-55.467 25.6-34.133 38.4-72.533 34.133-119.467-8.533-51.2-34.133-89.6-76.8-115.2-51.2-34.133-132.267-29.867-179.2 12.8-42.667 34.133-59.733 76.8-64 128 0 8.533-4.267 12.8-12.8 17.067-21.333 4.267-42.667 4.267-64 0-8.533-4.267-8.533-4.267-8.533-12.8 0-21.333-4.267-46.933-12.8-68.267-12.8-29.867-34.133-55.467-64-72.533-55.467-38.4-136.533-34.133-183.467 8.533-42.667 34.133-64 76.8-59.733 128 0 59.733 29.867 106.667 85.333 136.533s115.2 25.6 170.667-12.8c12.8-8.533 21.333-17.067 34.133-29.867 0-8.533 8.533-21.333 17.067-38.4zM307.2 302.933c-64 0-115.2-51.2-115.2-110.933 0-64 46.933-115.2 110.933-115.2s115.2 46.933 115.2 110.933c4.267 64-46.933 115.2-110.933 115.2zM712.533 302.933c-64 0-115.2-51.2-115.2-110.933 0-64 46.933-115.2 110.933-115.2 59.733 0 115.2 46.933 115.2 110.933 0 59.733-46.933 115.2-110.933 115.2zM1024 409.6c-341.333 0-682.667 0-1024 0 0 0 0 0 0 0 17.067 8.533 29.867 17.067 46.933 21.333 42.667 12.8 85.333 29.867 132.267 42.667 34.133 8.533 68.267 17.067 102.4 21.333 89.6 17.067 183.467 21.333 277.333 17.067 55.467-4.267 110.933-8.533 166.4-17.067 38.4-4.267 72.533-12.8 110.933-21.333s72.533-21.333 110.933-34.133c21.333-4.267 51.2-17.067 76.8-29.867 0 0 0 0 0 0z" />
<glyph unicode="&#xe95d;" glyph-name="zone" d="M243.2 448c-12.8 17.067-25.6 34.133-38.4 51.2-34.133 46.933-68.267 98.133-89.6 153.6-17.067 34.133-25.6 72.533-17.067 110.933 8.533 51.2 38.4 89.6 85.333 110.933 59.733 25.6 132.267 8.533 174.933-34.133 34.133-38.4 42.667-81.067 34.133-132.267-8.533-46.933-29.867-85.333-51.2-123.733-29.867-46.933-59.733-89.6-89.6-132.267-4.267 0-4.267 0-8.533-4.267zM247.467 823.467c-46.933 0-89.6-38.4-89.6-89.6 0-46.933 38.4-89.6 85.333-89.6s89.6 38.4 89.6 85.333c0 55.467-38.4 93.867-85.333 93.867zM490.667 379.733l-17.067 25.6 12.8 8.533-34.133 183.467c0 0 0 8.533-8.533 8.533l-42.667 4.267c0 0-68.267-110.933-157.867-217.6 4.267 4.267-93.867 110.933-132.267 187.733l-110.933-51.2c0 0-4.267 0-4.267-8.533l25.6-145.067 34.133-21.333-8.533-21.333-17.067 8.533 59.733-332.8 213.333 102.4 238.933-21.333-51.2 290.133zM149.333 285.867c-12.8 4.267-29.867 12.8-42.667 17.067 4.267 8.533 4.267 17.067 8.533 21.333 17.067 0 29.867-4.267 42.667-12.8-4.267-8.533-4.267-17.067-8.533-25.6zM256 268.8c-17.067 0-34.133 4.267-46.933 4.267 0 8.533 4.267 17.067 4.267 25.6 12.8 0 29.867-4.267 42.667-4.267 0-8.533 0-17.067 0-25.6zM315.733 277.333c-4.267 8.533-4.267 12.8-8.533 21.333 17.067 8.533 29.867 17.067 42.667 21.333 4.267-8.533 8.533-12.8 8.533-21.333-12.8-8.533-25.6-12.8-42.667-21.333zM405.333 328.533c-4.267 8.533-8.533 12.8-12.8 21.333 12.8 8.533 25.6 17.067 38.4 25.6 4.267-4.267 8.533-12.8 12.8-21.333-8.533-8.533-21.333-17.067-38.4-25.6zM972.8 460.8l-29.867 25.6 12.8 21.333 12.8-8.533-34.133 187.733c0 0 0 8.533-8.533 8.533l-226.133 17.067-209.067-93.867c0 0-8.533-4.267-4.267-12.8l29.867-170.667 21.333-12.8-17.067-17.067 55.467-307.2 213.333 102.4 234.667-21.333-51.2 281.6zM580.267 465.067c-4.267 4.267-8.533 12.8-12.8 17.067 12.8 12.8 21.333 21.333 29.867 34.133 4.267-4.267 12.8-12.8 17.067-17.067-12.8-8.533-25.6-21.333-34.133-34.133zM657.067 541.867c-4.267 4.267-8.533 12.8-12.8 21.333 12.8 8.533 25.6 17.067 38.4 25.6 8.533-8.533 12.8-17.067 12.8-21.333-12.8-8.533-25.6-17.067-38.4-25.6zM797.867 571.733c-12.8 4.267-25.6 4.267-42.667 4.267 0 8.533 0 17.067 0 25.6 17.067 0 34.133 0 51.2-4.267-4.267-8.533-4.267-17.067-8.533-25.6zM891.733 520.533c-12.8 8.533-25.6 17.067-38.4 25.6 4.267 8.533 8.533 12.8 12.8 21.333 12.8-8.533 25.6-17.067 38.4-25.6-4.267-8.533-8.533-12.8-12.8-21.333z" />
<glyph unicode="&#xe95e;" glyph-name="inventory" d="M273.067 226.133c4.267 0 8.533 4.267 8.533 8.533v85.333h98.133v-221.867h-217.6v221.867h98.133v-81.067c0-8.533 8.533-12.8 12.8-12.8zM512 226.133c4.267 0 8.533 4.267 8.533 8.533v85.333h98.133v-221.867h-217.6v221.867h98.133v-81.067c0-8.533 8.533-12.8 12.8-12.8zM750.933 226.133c4.267 0 8.533 4.267 8.533 8.533v85.333h98.133v-221.867h-217.6v221.867h98.133v-81.067c4.267-8.533 8.533-12.8 12.8-12.8zM644.267 780.8h98.133v-81.067c0-4.267 4.267-8.533 8.533-8.533s8.533 4.267 8.533 8.533v81.067h98.133v-221.867h-217.6v221.867h4.267zM401.067 780.8h98.133v-81.067c0-4.267 4.267-8.533 8.533-8.533s8.533 4.267 8.533 8.533v81.067h98.133v-221.867h-213.333v221.867zM162.133 780.8h98.133v-81.067c0-4.267 4.267-8.533 8.533-8.533s8.533 4.267 8.533 8.533v81.067h98.133v-221.867h-213.333v221.867zM153.6 537.6h780.8v-38.4h-844.8v38.4zM68.267-42.667h-42.667v981.333h42.667v-908.8zM89.6 38.4v38.4h844.8v-38.4zM998.4-42.667h-42.667v981.333h42.667z" />
<glyph unicode="&#xe95f;" glyph-name="latestBuys" d="M183.467 750.933h712.533v-38.4h-768v38.4zM89.6 64c8.533 0 12.8 0 21.333-4.267v900.267h-42.667v-900.267c8.533 4.267 12.8 4.267 21.333 4.267zM955.733 512v448h-42.667v-413.867c17.067-12.8 29.867-21.333 42.667-34.133zM145.067-8.533c0-30.633-24.833-55.467-55.467-55.467s-55.467 24.833-55.467 55.467c0 30.633 24.833 55.467 55.467 55.467s55.467-24.833 55.467-55.467zM418.133 426.667h-290.133v-38.4h273.067c4.267 17.067 8.533 29.867 17.067 38.4zM392.533 106.667h-264.533v-38.4h281.6c-8.533 12.8-12.8 25.6-17.067 38.4zM725.333 247.467c-12.8 0-21.333-8.533-21.333-21.333s8.533-21.333 21.333-21.333c12.8 0 21.333 8.533 21.333 21.333 0 8.533-12.8 21.333-21.333 21.333zM721.067 541.867c-166.4 0-298.667-136.533-298.667-302.933s132.267-302.933 298.667-302.933c166.4 0 298.667 136.533 298.667 302.933 0 170.667-132.267 302.933-298.667 302.933zM725.333 34.133c-98.133 0-174.933 72.533-187.733 162.133h-34.133l51.2 64 59.733-64h-38.4c8.533-68.267 72.533-123.733 149.333-123.733 81.067 0 149.333 64 149.333 145.067s-68.267 145.067-149.333 145.067c-68.267 0-128-46.933-145.067-110.933l-21.333 29.867-17.067-12.8c8.533 29.867 25.6 55.467 46.933 76.8l-25.6 21.333c-4.267 4.267-4.267 12.8 0 17.067l12.8 12.8c4.267 4.267 12.8 4.267 17.067 0l25.6-25.6c21.333 12.8 51.2 25.6 76.8 25.6v29.867h-8.533c-8.533 0-12.8 4.267-12.8 12.8v17.067c0 8.533 4.267 12.8 12.8 12.8h59.733c8.533 0 12.8-4.267 12.8-12.8v-17.067c0-8.533-4.267-12.8-12.8-12.8h-8.533v-21.333c29.867-4.267 55.467-12.8 81.067-29.867l34.133 29.867c4.267 4.267 12.8 4.267 17.067 0l12.8-12.8c4.267-4.267 4.267-12.8 0-17.067l-25.6-25.6c29.867-34.133 51.2-76.8 51.2-128 4.267-102.4-81.067-187.733-183.467-187.733zM772.267 226.133c0-25.6-21.333-46.933-46.933-46.933s-46.933 21.333-46.933 46.933c0 25.6 21.333 46.933 46.933 46.933 8.533 0 17.067-4.267 21.333-4.267l46.933 46.933 21.333-21.333-46.933-46.933c4.267-4.267 4.267-12.8 4.267-21.333z" />
<glyph unicode="&#xe968;" glyph-name="wiki" d="M793.6 733.867c0 0 4.267 0 4.267 0l76.8 12.8v-42.667c0-34.133-21.333-68.267-46.933-72.533 0 0-4.267 0-4.267 0l-76.8-12.8v42.667c0 34.133 21.333 64 46.933 72.533zM742.4 597.333l38.4 4.267c12.8 0 25.6-12.8 25.6-29.867v-21.333l-38.4-4.267c-12.8 0-25.6 12.8-25.6 29.867v21.333zM618.667 699.733l68.267 8.533c25.6 4.267 42.667-21.333 42.667-55.467v-38.4l-68.267-8.533c-25.6-4.267-42.667 21.333-42.667 55.467v38.4zM665.6 588.8c4.267 0 4.267 0 0 0l59.733 4.267v-29.867c0-25.6-17.067-46.933-34.133-55.467 0 0-4.267 0-4.267 0l-55.467-8.533v29.867c4.267 29.867 17.067 51.2 34.133 59.733zM443.733 648.533c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267-4.267-8.533-4.267-12.8-4.267zM443.733 512c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267 0-8.533-4.267-12.8-4.267zM443.733 379.733c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267 0-8.533-4.267-12.8-4.267zM443.733 247.467c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267 0-8.533-4.267-12.8-4.267zM588.8 379.733c-4.267 0-4.267 0-8.533 4.267s0 8.533 4.267 12.8c68.267 46.933 140.8 59.733 196.267 59.733s93.867-8.533 98.133-8.533c4.267 0 8.533-8.533 8.533-12.8s-8.533-8.533-12.8-8.533v0c0 0-153.6 38.4-277.333-46.933-4.267 4.267-4.267 0-8.533 0zM588.8 247.467c-4.267 0-4.267 0-8.533 4.267s0 8.533 4.267 12.8c68.267 46.933 140.8 59.733 196.267 59.733s93.867-8.533 98.133-8.533c4.267 0 8.533-8.533 8.533-12.8s-8.533-8.533-12.8-8.533v0c0 0-153.6 38.4-277.333-46.933-4.267 4.267-4.267 0-8.533 0zM985.6 738.133v64l-8.533 4.267c-4.267 0-81.067 29.867-179.2 29.867-106.667 0-200.533-34.133-277.333-98.133-76.8 64-170.667 98.133-277.333 98.133-102.4 0-174.933-29.867-179.2-29.867l-12.8-4.267v-59.733c-34.133-4.267-51.2-17.067-51.2-34.133v-614.4h452.267c17.067-12.8 38.4-21.333 64-21.333s46.933 8.533 64 21.333h443.733v614.4c0 17.067-17.067 25.6-38.4 29.867v0zM512 145.067c-38.4 17.067-166.4 64-298.667 64-51.2 0-98.133-8.533-136.533-21.333v597.333c21.333 8.533 85.333 25.6 162.133 25.6 98.133 0 183.467-29.867 256-89.6v-358.4l17.067 17.067v-234.667zM955.733 183.467c-42.667 17.067-89.6 25.6-140.8 25.6-128 0-251.733-51.2-290.133-64v238.933l17.067-17.067v349.867c68.267 59.733 153.6 89.6 256 89.6 76.8 0 136.533-17.067 162.133-25.6v-597.333z" />
<glyph unicode="&#xe96c;" glyph-name="attach" d="M960 866.133c-42.667 42.667-98.133 64-157.867 64s-115.2-21.333-157.867-64l-593.067-593.067c-34.133-34.133-55.467-85.333-51.2-136.533 0-42.667 17.067-81.067 46.933-110.933 34.133-38.4 81.067-59.733 132.267-59.733 46.933 0 93.867 17.067 128 51.2l541.867 546.133c25.6 25.6 42.667 64 42.667 98.133s-12.8 68.267-38.4 93.867c-25.6 25.6-59.733 38.4-98.133 38.4-34.133 0-72.533-17.067-98.133-42.667l-354.133-354.133c-4.267 0-4.267-4.267-4.267-12.8s4.267-12.8 8.533-17.067 25.6-8.533 34.133 0l354.133 354.133c12.8 17.067 38.4 25.6 59.733 25.6 25.6 0 51.2-12.8 68.267-34.133 8.533-12.8 17.067-25.6 17.067-42.667 4.267-25.6-4.267-55.467-25.6-72.533l-541.867-541.867c-25.6-25.6-55.467-38.4-93.867-38.4-34.133 0-68.267 12.8-93.867 38.4s-38.4 59.733-38.4 93.867c0 34.133 12.8 68.267 38.4 93.867l588.8 584.533c34.133 34.133 76.8 51.2 123.733 51.2s89.6-17.067 123.733-51.2c34.133-34.133 51.2-76.8 51.2-123.733s-17.067-89.6-51.2-123.733l-401.067-401.067c-4.267-4.267-8.533-12.8-8.533-17.067 0-8.533 4.267-12.8 8.533-17.067 8.533-8.533 25.6-8.533 34.133 0l401.067 401.067c89.6 89.6 89.6 230.4 4.267 320z" />
<glyph unicode="&#xe96d;" glyph-name="zone2" d="M98.133 17.067c-4.267 29.867-12.8 64-17.067 93.867-17.067 98.133-34.133 192-51.2 290.133-12.8 46.933-21.333 98.133-29.867 149.333 0 4.267 0 8.533 4.267 8.533 42.667 21.333 85.333 42.667 128 59.733 0 0 0 0 4.267 0 4.267-8.533 8.533-12.8 12.8-21.333-21.333-8.533-42.667-21.333-64-29.867-17.067-8.533-34.133-17.067-51.2-21.333-4.267 0-4.267-4.267-4.267-8.533 8.533-42.667 17.067-85.333 25.6-132.267 0-4.267 4.267-4.267 4.267-8.533 8.533-4.267 17.067-8.533 25.6-17.067 0-4.267-4.267-12.8-8.533-17.067-4.267 4.267-12.8 4.267-17.067 8.533 17.067-102.4 38.4-209.067 55.467-311.467 17.067 8.533 29.867 12.8 42.667 21.333 51.2 25.6 102.4 51.2 153.6 72.533 4.267 0 8.533 0 17.067 0 68.267-4.267 136.533-12.8 204.8-17.067 0 0 4.267 0 8.533 0-4.267 17.067-4.267 34.133-8.533 51.2-12.8 68.267-25.6 136.533-38.4 204.8 0 8.533-4.267 17.067-12.8 25.6-4.267 4.267-4.267 8.533-8.533 12.8 12.8 4.267 8.533 17.067 8.533 25.6-8.533 51.2-17.067 106.667-29.867 157.867 0 4.267 0 4.267-8.533 4.267-17.067 0-38.4 4.267-55.467 4.267 4.267 8.533 8.533 17.067 12.8 21.333 0 0 4.267 4.267 8.533 4.267 17.067 0 34.133-4.267 46.933-4.267 4.267 0 8.533 0 12.8 0 68.267 29.867 132.267 64 200.533 93.867 4.267 4.267 8.533 8.533 12.8 8.533 68.267-4.267 136.533-8.533 204.8-17.067 8.533 0 17.067 0 29.867-4.267 4.267 0 8.533-4.267 8.533-8.533 12.8-64 25.6-132.267 34.133-196.267 17.067-102.4 38.4-204.8 55.467-311.467 0-8.533 4.267-17.067 4.267-25.6-17.067 0-34.133 4.267-51.2 4.267-42.667 4.267-89.6 8.533-132.267 12.8-17.067 0-38.4 4.267-55.467 4.267-4.267 0-12.8 0-17.067-4.267-68.267-29.867-132.267-64-200.533-93.867 0 0-4.267 0-8.533 0-76.8 8.533-149.333 12.8-226.133 21.333-4.267 0-8.533 0-12.8 0-72.533-34.133-140.8-68.267-213.333-102.4 0-4.267 0-8.533-4.267-8.533zM989.867 217.6c0 4.267 0 4.267 0 8.533-8.533 34.133-12.8 72.533-21.333 106.667-8.533 46.933-17.067 89.6-25.6 136.533 0 8.533-4.267 12.8-8.533 17.067-8.533 4.267-12.8 12.8-21.333 17.067 4.267 8.533 8.533 12.8 12.8 17.067 4.267-4.267 8.533-4.267 12.8-8.533-4.267 12.8-4.267 21.333-4.267 34.133-8.533 46.933-17.067 93.867-25.6 145.067 0 4.267-4.267 8.533-8.533 8.533-68.267 4.267-136.533 12.8-209.067 17.067-4.267 0-8.533 0-12.8-4.267-64-29.867-123.733-59.733-187.733-85.333-4.267-4.267-8.533-4.267-4.267-12.8 4.267-29.867 12.8-64 17.067-93.867 4.267-21.333 8.533-42.667 12.8-59.733 12.8-4.267 12.8-12.8 21.333-21.333-12.8-4.267-12.8-12.8-12.8-25.6 12.8-68.267 25.6-132.267 38.4-200.533 4.267-25.6 8.533-51.2 12.8-76.8 4.267 0 4.267 0 8.533 4.267 59.733 29.867 119.467 59.733 183.467 89.6-4.267 4.267 0 4.267 4.267 4.267 51.2-4.267 106.667-8.533 157.867-12.8 21.333 0 38.4-4.267 59.733-4.267zM260.267 469.333c-12.8 17.067-25.6 34.133-38.4 46.933-29.867 46.933-59.733 93.867-85.333 145.067-12.8 29.867-21.333 64-17.067 102.4 8.533 51.2 34.133 85.333 81.067 102.4 55.467 21.333 123.733 8.533 162.133-34.133 34.133-34.133 42.667-76.8 34.133-123.733-8.533-42.667-25.6-76.8-46.933-115.2-25.6-42.667-55.467-81.067-85.333-123.733 0 4.267-4.267 0-4.267 0zM260.267 819.2c-46.933 0-81.067-34.133-81.067-81.067s38.4-81.067 81.067-81.067c46.933 0 81.067 38.4 85.333 81.067 0 42.667-38.4 81.067-85.333 81.067zM358.4 349.867c4.267-8.533 4.267-12.8 8.533-21.333-12.8-4.267-25.6-12.8-38.4-17.067-4.267 8.533-4.267 12.8-8.533 21.333 12.8 4.267 25.6 12.8 38.4 17.067zM226.133 302.933c0 8.533 4.267 17.067 4.267 21.333 12.8 0 25.6-4.267 38.4-4.267 0-8.533 0-12.8 0-25.6-12.8 8.533-29.867 8.533-42.667 8.533zM413.867 354.133c-4.267 8.533-8.533 12.8-12.8 21.333 12.8 8.533 21.333 17.067 34.133 25.6 4.267-4.267 8.533-12.8 12.8-17.067-12.8-12.8-21.333-21.333-34.133-29.867zM179.2 341.333c-4.267-8.533-4.267-12.8-8.533-21.333-12.8 4.267-25.6 8.533-38.4 17.067 4.267 8.533 4.267 12.8 8.533 21.333 12.8-8.533 25.6-12.8 38.4-17.067zM682.667 580.267c-12.8-8.533-21.333-17.067-34.133-21.333-4.267 4.267-8.533 12.8-12.8 17.067 12.8 8.533 25.6 17.067 38.4 25.6 4.267-8.533 4.267-17.067 8.533-21.333zM878.933 558.933c-4.267-8.533-8.533-12.8-12.8-21.333-12.8 8.533-25.6 17.067-34.133 21.333 4.267 8.533 8.533 12.8 8.533 21.333 12.8-8.533 25.6-17.067 38.4-21.333zM571.733 486.4c-4.267 4.267-8.533 12.8-12.8 17.067 8.533 8.533 21.333 21.333 29.867 29.867 4.267-4.267 8.533-12.8 12.8-17.067-8.533-8.533-17.067-21.333-29.867-29.867zM785.067 610.133c-4.267-8.533-4.267-17.067-8.533-21.333-12.8 0-25.6 4.267-38.4 4.267 0 8.533 0 12.8 0 21.333 17.067 0 29.867-4.267 46.933-4.267z" />

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,87 @@
module.exports = Self => {
Self.remoteMethod('editLatestBuys', {
description: 'Updates a column for one or more buys',
accessType: 'WRITE',
accepts: [{
arg: 'field',
type: 'String',
required: true,
description: `the column to edit`
},
{
arg: 'newValue',
type: 'Any',
required: true,
description: `The new value to save`
},
{
arg: 'lines',
type: ['Object'],
required: true,
description: `the buys which will be modified`
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/editLatestBuys`,
verb: 'POST'
}
});
Self.editLatestBuys = async(field, newValue, lines) => {
let modelName;
let identifier;
switch (field) {
case 'size':
case 'density':
case 'minPrice':
case 'description':
modelName = 'Item';
identifier = 'itemFk';
break;
case 'quantity':
case 'buyingValue':
case 'freightValue':
case 'packing':
case 'grouping':
case 'groupingMode':
case 'comissionValue':
case 'packageValue':
case 'price2':
case 'price3':
case 'weight':
modelName = 'Buy';
identifier = 'id';
}
const models = Self.app.models;
const model = models[modelName];
let tx = await model.beginTransaction({});
try {
let promises = [];
let options = {transaction: tx};
let targets = lines.map(line => {
return line[identifier];
});
let value = {};
value[field] = newValue;
// intentarlo con updateAll
for (let target of targets)
promises.push(model.upsertWithWhere({id: target}, value, options));
await Promise.all(promises);
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
};
};

View File

@ -0,0 +1,171 @@
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('latestBuysFilter', {
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',
},
{
arg: 'search',
type: 'String',
description: `If it's and integer searchs by id, otherwise it searchs by name`,
}, {
arg: 'id',
type: 'Integer',
description: 'Item id',
}, {
arg: 'tags',
type: ['Object'],
description: 'List of tags to filter with',
http: {source: 'query'}
}, {
arg: 'description',
type: 'String',
description: 'The item description',
}, {
arg: 'salesPersonFk',
type: 'Integer',
description: 'The buyer of the item',
}, {
arg: 'active',
type: 'Boolean',
description: 'Whether the the item is or not active',
}, {
arg: 'visible',
type: 'Boolean',
description: 'Whether the the item is or not visible',
}, {
arg: 'typeFk',
type: 'Integer',
description: 'Type id',
}, {
arg: 'categoryFk',
type: 'Integer',
description: 'Category id',
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/latestBuysFilter`,
verb: 'GET'
}
});
Self.latestBuysFilter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'i.id': value}
: {'i.name': {like: `%${value}%`}};
case 'id':
return {'i.id': value};
case 'description':
return {'i.description': {like: `%${value}%`}};
case 'categoryFk':
return {'ic.id': value};
case 'salesPersonFk':
return {'it.workerFk': value};
case 'typeFk':
return {'i.typeFk': value};
case 'active':
return {'i.isActive': value};
case 'visible':
if (value)
return {'v.visible': {gt: 0}};
else if (!value)
return {'v.visible': {lte: 0}};
}
});
filter = mergeFilters(ctx.args.filter, {where});
let stmts = [];
let stmt;
stmts.push('CALL cache.last_buy_refresh(FALSE)');
stmts.push('CALL cache.visible_refresh(@calc_id, FALSE, 1)');
stmt = new ParameterizedSQL(`
SELECT
i.image,
i.id AS itemFk,
i.size,
i.density,
i.typeFk,
i.family,
i.isActive,
i.minPrice,
i.description,
t.name AS type,
intr.description AS intrastat,
ori.code AS origin,
b.entryFk,
b.id,
b.quantity,
b.buyingValue,
b.freightValue,
b.isIgnored,
b.packing,
b.grouping,
b.groupingMode,
b.comissionValue,
b.packageValue,
b.price2,
b.price3,
b.ektFk,
b.weight
FROM cache.last_buy lb
LEFT JOIN cache.visible v ON v.item_id = lb.item_id
AND v.calc_id = @calc_id
JOIN item i ON i.id = lb.item_id
JOIN itemType it ON it.id = i.typeFk AND lb.warehouse_id = it.warehouseFk
JOIN buy b ON b.id = lb.buy_id
LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
LEFT JOIN itemType t ON t.id = i.typeFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN origin ori ON ori.id = i.originFk`
);
if (ctx.args.tags) {
let i = 1;
for (const tag of ctx.args.tags) {
const tAlias = `it${i++}`;
if (tag.tagFk) {
stmt.merge({
sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.tagFk = ?
AND ${tAlias}.value LIKE ?`,
params: [tag.tagFk, `%${tag.value}%`],
});
} else {
stmt.merge({
sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.value LIKE ?`,
params: [`%${tag.value}%`],
});
}
}
}
stmt.merge(conn.makeSuffix(filter));
let buysIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
return buysIndex === 0 ? result : result[buysIndex];
};
};

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
const model = app.models.Buy;
describe('Buy editLatestsBuys()', () => {
it('should change the value of a given column for the selected buys', async() => {
let ctx = {
args: {
search: 'Ranged weapon longbow 2m'
}
};
let [original] = await model.latestBuysFilter(ctx);
const field = 'size';
let newValue = 99;
const lines = [{itemFk: original.itemFk, id: original.id}];
await model.editLatestBuys(field, newValue, lines);
let [result] = await model.latestBuysFilter(ctx);
expect(result.size).toEqual(99);
newValue = original.size;
await model.editLatestBuys(field, newValue, lines);
let [restoredFixture] = await model.latestBuysFilter(ctx);
expect(restoredFixture.size).toEqual(original.size);
});
});

View File

@ -0,0 +1,139 @@
const app = require('vn-loopback/server/server');
describe('Buy latests buys filter()', () => {
it('should return the entry matching "search"', async() => {
let ctx = {
args: {
search: 'Ranged weapon longbow 2m'
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
let firstBuy = results[0];
expect(results.length).toEqual(1);
expect(firstBuy.size).toEqual(70);
});
it('should return the entry matching "id"', async() => {
let ctx = {
args: {
id: 1
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toEqual(1);
});
it('should return results matching "tags"', async() => {
let ctx = {
args: {
tags: [
{tagFk: 27, value: '2m'}
]
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(2);
});
it('should return results matching "categoryFk"', async() => {
let ctx = {
args: {
categoryFk: 1
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(4);
});
it('should return results matching "typeFk"', async() => {
let ctx = {
args: {
typeFk: 2
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(4);
});
it('should return results matching "active"', async() => {
let ctx = {
args: {
active: true
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(6);
});
it('should return results matching "not active"', async() => {
let ctx = {
args: {
active: false
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(0);
});
it('should return results matching "visible"', async() => {
let ctx = {
args: {
visible: true
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(5);
});
it('should return results matching "not visible"', async() => {
let ctx = {
args: {
visible: false
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(0);
});
it('should return results matching "salesPersonFk"', async() => {
let ctx = {
args: {
salesPersonFk: 35
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(6);
});
it('should return results matching "description"', async() => {
let ctx = {
args: {
description: 'Increases block'
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(1);
});
});

View File

@ -2,6 +2,9 @@
"Entry": {
"dataSource": "vn"
},
"Buy": {
"dataSource": "vn"
},
"EntryLog": {
"dataSource": "vn"
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/entry/editLatestBuys')(Self);
require('../methods/entry/latestBuysFilter')(Self);
};

View File

@ -0,0 +1,61 @@
{
"name": "Buy",
"base": "Loggable",
"log": {
"model": "EntryLog",
"relation": "entry"
},
"options": {
"mysql": {
"table": "buy"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"quantity": {
"type": "number"
},
"buyingValue": {
"type": "number"
},
"freightValue": {
"type": "number"
},
"packing": {
"type": "number"
},
"grouping": {
"type": "number"
},
"groupingMode": {
"type": "number"
},
"comissionValue": {
"type": "number"
},
"packageValue": {
"type": "number"
},
"price2": {
"type": "number"
},
"price3": {
"type": "number"
},
"weight": {
"type": "number"
}
},
"relations": {
"entry": {
"type": "belongsTo",
"model": "Entry",
"foreignKey": "entryFk",
"required": true
}
}
}

View File

@ -1,4 +1,3 @@
module.exports = Self => {
require('../methods/entry/filter')(Self);
require('../methods/entry/getEntry')(Self);

View File

@ -1,6 +1,6 @@
{
"name": "Entry",
"base": "VnModel",
"base": "Loggable",
"log": {
"model":"EntryLog"
},
@ -62,6 +62,18 @@
},
"loadPriority": {
"type": "number"
},
"supplierFk": {
"type": "number",
"required": true
},
"travelFk": {
"type": "number",
"required": true
},
"companyFk": {
"type": "number",
"required": true
}
},
"relations": {

View File

@ -0,0 +1,10 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnEntryBasicData', {
template: require('./index.html'),
controller: Section,
bindings: {
entry: '<'
}
});

View File

@ -10,7 +10,8 @@ class Controller extends ModuleCard {
scope: {
fields: ['id', 'code']
}
}, {
},
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
@ -35,12 +36,14 @@ class Controller extends ModuleCard {
}
]
}
}, {
},
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
}, {
},
{
relation: 'currency'
}
]

View File

@ -0,0 +1,61 @@
<mg-ajax path="Entries" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.entry"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-icon color-marginal
icon="info"
vn-tooltip="Required fields (*)">
</vn-icon>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.entry.supplierFk"
url="Suppliers"
show-field="nickname"
search-function="{or: [{id: $search}, {nickname: {like: '%'+ $search +'%'}}]}"
value-field="id"
order="nickname"
label="Supplier"
required="true">
<tpl-item>
{{::id}} - {{::nickname}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.entry.travelFk"
url="Travels/filter"
search-function="$ctrl.searchFunction($search)"
value-field="id"
order="id"
label="Travel"
required="true">
<tpl-item>
{{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) &#x2192;
{{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}})
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
url="Companies"
label="Company"
show-field="code"
value-field="id"
ng-model="$ctrl.entry.companyFk"
required="true">
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Create"></vn-submit>
<vn-button ui-sref="entry.index" label="Cancel"></vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,43 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.entry = {
companyFk: this.vnConfig.companyFk
};
if (this.$params && this.$params.supplierFk)
this.entry.supplierFk = parseInt(this.$params.supplierFk);
if (this.$params && this.$params.travelFk)
this.entry.travelFk = parseInt(this.$params.travelFk);
if (this.$params && this.$params.companyFk)
this.entry.companyFk = parseInt(this.$params.companyFk);
}
onSubmit() {
this.$.watcher.submit().then(
res => this.$state.go('entry.card.basicData', {id: res.data.id})
);
}
searchFunction($search) {
return {or: [
{'agencyModeName': {like: `%${$search}%`}},
{'warehouseInName': {like: `%${$search}%`}},
{'warehouseOutName': {like: `%${$search}%`}},
{'shipped': new Date($search)},
{'landed': new Date($search)}
]};
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnEntryCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,2 @@
New entry: Nueva entrada
Required fields (*): Campos requeridos (*)

View File

@ -0,0 +1,10 @@
vn-entry-create {
vn-card {
position: relative
}
vn-icon[icon="info"] {
position: absolute;
top: 16px;
right: 16px
}
}

View File

@ -0,0 +1,3 @@
<slot-descriptor>
<vn-entry-descriptor></vn-entry-descriptor>
</slot-descriptor>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {}
ngModule.vnComponent('vnEntryDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -11,15 +11,23 @@ class Controller extends Descriptor {
}
get travelFilter() {
return this.entry && JSON.stringify({
agencyFk: this.entry.travel.agencyFk
});
let travelFilter;
const entryTravel = this.entry && this.entry.travel;
if (entryTravel && entryTravel.agencyFk) {
travelFilter = this.entry && JSON.stringify({
agencyFk: entryTravel.agencyFk
});
}
return travelFilter;
}
get entryFilter() {
if (!this.entry) return null;
let entryTravel = this.entry && this.entry.travel;
const date = new Date(this.entry.travel.landed);
if (!entryTravel || !entryTravel.landed) return null;
const date = new Date(entryTravel.landed);
date.setHours(0, 0, 0, 0);
const from = new Date(date.getTime());
@ -35,6 +43,48 @@ class Controller extends Descriptor {
});
}
loadData() {
const filter = {
include: [
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
}
]
};
return this.getData(`Entries/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
showEntryReport() {
this.vnReport.show('entry-order', {
entryId: this.entry.id

View File

@ -1,12 +1,14 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpBackend;
let controller;
const entry = {id: 2};
beforeEach(ngModule('entry'));
beforeEach(inject($componentController => {
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnEntryDescriptor', {$element: null}, {entry});
}));
@ -24,4 +26,18 @@ describe('Entry Component vnEntryDescriptor', () => {
expect(controller.vnReport.show).toHaveBeenCalledWith('entry-order', params);
});
});
describe('loadData()', () => {
it('should perform ask for the entry', () => {
let query = `Entries/${entry.id}`;
jest.spyOn(controller, 'getData');
$httpBackend.expectGET(query).respond();
controller.loadData();
$httpBackend.flush();
expect(controller.getData).toHaveBeenCalledTimes(1);
expect(controller.getData).toHaveBeenCalledWith(query, jasmine.any(Object));
});
});
});

View File

@ -2,8 +2,12 @@ export * from './module';
import './main';
import './index/';
import './create';
import './latest-buys';
import './search-panel';
import './latest-buys-search-panel';
import './descriptor';
import './descriptor-popover';
import './card';
import './summary';
import './log';

View File

@ -69,4 +69,16 @@
</vn-data-viewer>
<vn-travel-descriptor-popover
vn-id="travelDescriptor">
</vn-travel-descriptor-popover>
</vn-travel-descriptor-popover>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<a ui-sref="entry.create" vn-bind="+">
<vn-button class="round md vn-mb-sm"
icon="add"
vn-tooltip="New entry"
tooltip-position="left">
</vn-button>
</a>
</vn-vertical>
</div>

View File

@ -12,4 +12,6 @@ Reference: Referencia
Created: Creado
Booked: Facturado
Is inventory: Inventario
Notes: Notas
Notes: Notas
Status: Estado
Selection: Selección

View File

@ -0,0 +1,167 @@
<mg-ajax path="Tags" options="mgIndex as tags"></mg-ajax>
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
label="General search"
ng-model="filter.search"
info="Search items by id, name or barcode"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-focus
url="ItemCategories"
label="Category"
show-field="name"
value-field="id"
ng-model="filter.categoryFk">
</vn-autocomplete>
<vn-autocomplete
url="ItemTypes"
label="Type"
where="{categoryFk: filter.categoryFk}"
show-field="name"
value-field="id"
ng-model="filter.typeFk"
fields="['categoryFk']"
include="'category'">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{category.name}}
</div>
</tpl-item>>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
disabled="false"
ng-model="filter.salesPersonFk"
url="Clients/activeWorkersWithRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Buyer">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="Is active"
ng-model="filter.active"
triple-state="true">
</vn-check>
<vn-check
label="Is visible"
ng-model="filter.visible"
triple-state="true">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
Tags
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add tag"
icon="add_circle"
ng-click="filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-repeat="itemTag in filter.tags">
<vn-autocomplete
vn-id="tag"
ng-model="itemTag.tagFk"
data="tags.model"
show-field="name"
label="Tag"
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
ng-show="tag.selection.isFree !== false"
vn-id="text"
label="Value"
ng-model="itemTag.value">
</vn-textfield>
<vn-autocomplete
ng-show="tag.selection.isFree === false"
url="{{$ctrl.getSourceTable(tag.selection)}}"
label="Value"
ng-model="itemTag.value"
show-field="name"
value-field="name">
</vn-autocomplete>
<vn-icon-button
vn-none
vn-tooltip="Remove tag"
icon="delete"
ng-click="filter.tags.splice($index, 1)"
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
More fields
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add field"
icon="add_circle"
ng-click="$ctrl.fieldFilters.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-repeat="fieldFilter in $ctrl.fieldFilters">
<vn-autocomplete
label="Field"
ng-model="fieldFilter.name"
data="$ctrl.moreFields"
value-field="name"
show-field="label"
show-filter="false"
translate-fields="['label']"
selection="info"
on-change="fieldFilter.value = null">
</vn-autocomplete>
<vn-one ng-switch="info.type">
<div ng-switch-when="Number">
<vn-input-number
label="Value"
ng-model="fieldFilter.value">
</vn-input-number>
</div>
<div ng-switch-when="Boolean">
<vn-check
label="Value"
ng-model="fieldFilter.value">
</vn-check>
</div>
<div ng-switch-when="Date">
<vn-date-picker
label="Value"
ng-model="fieldFilter.value">
</vn-date-picker>
</div>
<div ng-switch-default>
<vn-textfield
label="Value"
ng-model="fieldFilter.value">
</vn-textfield>
</div>
</vn-one>
<vn-icon-button
vn-none
vn-tooltip="Remove field"
icon="delete"
ng-click="$ctrl.removeField($index, fieldFilter.name)"
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,79 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
constructor($element, $) {
super($element, $);
let model = 'Item';
let moreFields = ['description', 'name'];
let properties;
let validations = window.validations;
if (validations && validations[model])
properties = validations[model].properties;
else
properties = {};
this.moreFields = [];
for (let field of moreFields) {
let prop = properties[field];
this.moreFields.push({
name: field,
label: prop ? prop.description : field,
type: prop ? prop.type : null
});
}
}
get filter() {
let filter = this.$.filter;
for (let fieldFilter of this.fieldFilters)
filter[fieldFilter.name] = fieldFilter.value;
return filter;
}
set filter(value) {
if (!value)
value = {};
if (!value.tags)
value.tags = [{}];
this.fieldFilters = [];
for (let field of this.moreFields) {
if (value[field.name] != undefined) {
this.fieldFilters.push({
name: field.name,
value: value[field.name],
info: field
});
}
}
this.$.filter = value;
}
getSourceTable(selection) {
if (!selection || selection.isFree === true)
return null;
if (selection.sourceTable) {
return ''
+ selection.sourceTable.charAt(0).toUpperCase()
+ selection.sourceTable.substring(1) + 's';
} else if (selection.sourceTable == null)
return `ItemTags/filterItemTags/${selection.id}`;
}
removeField(index, field) {
this.fieldFilters.splice(index, 1);
this.$.filter[field] = undefined;
}
}
ngModule.component('vnLatestBuysSearchPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,45 @@
import './index.js';
describe('Entry', () => {
describe('Component vnLatestBuysSearchPanel', () => {
let $element;
let controller;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject($componentController => {
$element = angular.element(`<vn-latest-buys-search-panel></vn-latest-buys-search-panel>`);
controller = $componentController('vnLatestBuysSearchPanel', {$element});
}));
describe('getSourceTable()', () => {
it(`should return null if there's no selection`, () => {
let selection = null;
let result = controller.getSourceTable(selection);
expect(result).toBeNull();
});
it(`should return null if there's a selection but its isFree property is truthy`, () => {
let selection = {isFree: true};
let result = controller.getSourceTable(selection);
expect(result).toBeNull();
});
it(`should return the formated sourceTable concatenated to a path`, () => {
let selection = {sourceTable: 'hello guy'};
let result = controller.getSourceTable(selection);
expect(result).toEqual('Hello guys');
});
it(`should return a path if there's no sourceTable and the selection has an id`, () => {
let selection = {id: 99};
let result = controller.getSourceTable(selection);
expect(result).toEqual(`ItemTags/filterItemTags/${selection.id}`);
});
});
});
});

View File

@ -0,0 +1,177 @@
<vn-crud-model
vn-id="model"
url="Buys/latestBuysFilter"
limit="20"
data="$ctrl.buys">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
panel="vn-latest-buys-search-panel"
placeholder="Search by item id or name"
info="You can search by item id or name"
suggested-filter="{isActive: true}"
model="model"
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
<vn-card>
<vn-table
model="model"
show-fields="$ctrl.showFields"
vn-smart-table="latestBuys">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th field="Image">Picture</vn-th>
<vn-th smart-table-ignore field="id">Id</vn-th>
<vn-th field="packing">Packing</vn-th>
<vn-th field="grouping">Grouping</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="size">Size</vn-th>
<vn-th field="type">Type</vn-th>
<vn-th field="intrastat">Intrastat</vn-th>
<vn-th field="origin">Origin</vn-th>
<vn-th field="density">Density</vn-th>
<vn-th field="isActive">Active</vn-th>
<vn-th field="family">Family</vn-th>
<vn-th field="entryFk">Entry</vn-th>
<vn-th field="quantity">Quantity</vn-th>
<vn-th field="buyingValue">Buying value</vn-th>
<vn-th field="freightValue">Freight value</vn-th>
<vn-th field="comissionValue" expand>Commission value</vn-th>
<vn-th field="packageValue" expand>Package value</vn-th>
<vn-th field="isIgnored">Is ignored</vn-th>
<vn-th expand field="price2">Grouping price</vn-th>
<vn-th expand field="price3">Packing price</vn-th>
<vn-th field="minPrice">Min price</vn-th>
<vn-th field="ektFk">Ekt</vn-th>
<vn-th field="weight">Weight</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="buy in $ctrl.buys"
class="clickable vn-tr search-result"
ui-sref="entry.card.buy({id: {{::buy.entryFk}}})">
<vn-td shrink>
<vn-check
ng-model="buy.checked"
vn-click-stop>
</vn-check>
</vn-td>
<vn-td shrink >
<img
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::buy.image}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::buy.image}}"
vn-click-stop
on-error-src/>
</vn-td>
<vn-td shrink>
<span
vn-click-stop="itemDescriptor.show($event, buy.itemFk)"
class="link">
{{::buy.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<vn-chip translate-attr="buy.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 2}">
<span translate>{{::buy.packing | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<vn-td number>
<vn-chip translate-attr="buy.groupingMode == 1 ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 1}">
<span translate>{{::buy.grouping | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<vn-td vn-two title="{{::buy.description}}">
{{::buy.description | dashIfEmpty}}
</vn-td>
<vn-td number>{{::buy.size}}</vn-td>
<vn-td shrink title="{{::buy.type}}">
{{::buy.type}}
</vn-td>
<vn-td shrink title="{{::item.intrastat}}">
{{::buy.intrastat}}
</vn-td>
<vn-td shrink>{{::buy.origin}}</vn-td>
<vn-td shrink>{{::buy.density}}</vn-td>
<vn-td shrink>
<vn-check
disabled="true"
ng-model="::buy.isActive">
</vn-check>
</vn-td>
<vn-td shrink>{{::buy.family}}</vn-td>
<vn-td shrink>
<span
vn-click-stop="entryDescriptor.show($event, buy.entryFk)"
class="link">
{{::buy.entryFk}}
</span>
</vn-td>
<vn-td number>{{::buy.quantity}}</vn-td>
<vn-td number>{{::buy.buyingValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.freightValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.comissionValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.packageValue | currency: 'EUR':2}}</vn-td>
<vn-td shrink>{{::buy.isIgnored}}</vn-td>
<vn-td number>{{::buy.price2 | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.price3 | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.minPrice | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.ektFk | dashIfEmpty}}</vn-td>
<vn-td number>{{::buy.weight}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<vn-button class="round sm vn-mb-sm"
icon="edit"
ng-show="$ctrl.totalChecked > 0"
ng-click="edit.show($event)"
vn-tooltip="Edit buy(s)"
tooltip-position="left">
</vn-button>
</vn-vertical>
</div>
<vn-dialog class="edit"
vn-id="edit"
on-accept="$ctrl.onEditAccept()"
on-close="$ctrl.editedColumn = null"
message="Edit buy(s)">
<tpl-body>
<vn-horizontal>
<vn-autocomplete
vn-two
ng-model="$ctrl.editedColumn.field"
data="$ctrl.columns"
value-field="field"
show-field="displayName"
label="Field to edit">
</vn-autocomplete>
<vn-textfield
vn-one
label="Value"
ng-model="$ctrl.editedColumn.newValue">
</vn-textfield>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button>
</tpl-buttons>
</vn-dialog>
<vn-item-descriptor-popover
vn-id="itemDescriptor">
</vn-item-descriptor-popover>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>

View File

@ -0,0 +1,78 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.showFields = {
id: false,
actions: false
};
this.editedColumn;
}
get columns() {
if (this._columns) return this._columns;
this._columns = [
{field: 'quantity', displayName: this.$t('Quantity')},
{field: 'buyingValue', displayName: this.$t('Buying value')},
{field: 'freightValue', displayName: this.$t('Freight value')},
{field: 'packing', displayName: this.$t('Packing')},
{field: 'grouping', displayName: this.$t('Grouping')},
{field: 'comissionValue', displayName: this.$t('Commission value')},
{field: 'packageValue', displayName: this.$t('Package value')},
{field: 'price2', displayName: this.$t('Grouping price')},
{field: 'price3', displayName: this.$t('Packing price')},
{field: 'weight', displayName: this.$t('Weight')},
{field: 'description', displayName: this.$t('Description')},
{field: 'minPrice', displayName: this.$t('Min price')},
{field: 'size', displayName: this.$t('Size')},
{field: 'density', displayName: this.$t('Density')}
];
return this._columns;
}
get checked() {
const buys = this.$.model.data || [];
const checkedBuys = [];
for (let buy of buys) {
if (buy.checked)
checkedBuys.push(buy);
}
return checkedBuys;
}
uncheck() {
const lines = this.checked;
for (let line of lines) {
if (line.checked)
line.checked = false;
}
}
get totalChecked() {
return this.checked.length;
}
onEditAccept() {
let data = {
field: this.editedColumn.field,
newValue: this.editedColumn.newValue,
lines: this.checked
};
return this.$http.post('Buys/editLatestBuys', data)
.then(() => {
this.uncheck();
this.$.model.refresh();
});
}
}
ngModule.component('vnEntryLatestBuys', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,82 @@
import './index.js';
describe('Entry', () => {
describe('Component vnEntryLatestBuys', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $element = $compile('<vn-entry-latest-buys></vn-entry-latest-buys')($rootScope);
controller = $componentController('vnEntryLatestBuys', {$element});
controller.$ = {
model: {refresh: () => {}},
edit: {hide: () => {}}
};
}));
describe('get columns', () => {
it(`should return a set of columns`, () => {
let result = controller.columns;
let length = result.length;
let anyColumn = Object.keys(result[Math.floor(Math.random() * Math.floor(length))]);
expect(anyColumn).toContain('field', 'displayName');
});
});
describe('get checked', () => {
it(`should return a set of checked lines`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
});
});
describe('uncheck()', () => {
it(`should clear the selection of lines on the controller`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
controller.uncheck();
result = controller.checked;
expect(result.length).toEqual(0);
});
});
describe('onEditAccept()', () => {
it(`should perform a query to update columns`, () => {
controller.editedColumn = {field: 'my field', newValue: 'the new value'};
let query = 'Buys/editLatestBuys';
$httpBackend.expectPOST(query).respond();
controller.onEditAccept();
$httpBackend.flush();
const result = controller.checked;
expect(result.length).toEqual(0);
});
});
});
});

View File

@ -0,0 +1 @@
Minimun amount: Minimun purchase quantity

View File

@ -0,0 +1,13 @@
Edit buy(s): Editar compra(s)
Buying value: Precio
Freight value: Porte
Commission value: Comisión
Package value: Embalaje
Is ignored: Ignorado
Grouping price: Precio grouping
Packing price: Precio packing
Min price: Precio min
Ekt: Ekt
Weight: Peso
Minimun amount: Cantidad mínima de compra
Field to edit: Campo a editar

View File

@ -1,5 +1,6 @@
#Ordenar alfabeticamente
entry: entrada
Latest buys: Últimas compras
# Sections

View File

@ -2,13 +2,15 @@
"module": "entry",
"name": "Entries",
"icon": "icon-entry",
"dependencies": ["travel"],
"dependencies": ["travel", "item"],
"validations": true,
"menus": {
"main": [
{"state": "entry.index", "icon": "icon-entry"}
{"state": "entry.index", "icon": "icon-entry"},
{"state": "entry.latestBuys", "icon": "icon-latestBuys"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},
{"state": "entry.card.buy", "icon": "icon-lines"},
{"state": "entry.card.log", "icon": "history"}
]
@ -24,7 +26,20 @@
"url": "/index?q",
"state": "entry.index",
"component": "vn-entry-index",
"description": "Entries"
"description": "Entries",
"acl": ["buyer"]
}, {
"url": "/latest-buys?q",
"state": "entry.latestBuys",
"component": "vn-entry-latest-buys",
"description": "Latest buys",
"acl": ["buyer"]
}, {
"url": "/create?supplierFk&travelFk&companyFk",
"state": "entry.create",
"component": "vn-entry-create",
"description": "New entry",
"acl": ["buyer"]
}, {
"url": "/:id",
"state": "entry.card",
@ -38,6 +53,14 @@
"params": {
"entry": "$ctrl.entry"
}
}, {
"url": "/basic-data",
"state": "entry.card.basicData",
"component": "vn-entry-basic-data",
"description": "Basic data",
"params": {
"entry": "$ctrl.entry"
}
}, {
"url" : "/log",
"state": "entry.card.log",

View File

@ -26,7 +26,9 @@ module.exports = Self => {
await fs.mkdir(tempPath, {recursive: true});
const timer = setInterval(async() => {
const image = await Self.findOne({where: {error: null}});
const image = await Self.findOne({
where: {error: null, url: {neq: null}}
});
// Exit loop
if (!image) return clearInterval(timer);

View File

@ -12,42 +12,38 @@ module.exports = Self => {
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}, {
arg: 'tags',
type: ['Object'],
description: 'List of tags to filter with',
http: {source: 'query'}
}, {
arg: 'search',
type: 'String',
description: `If it's and integer searchs by id, otherwise it searchs by name`,
http: {source: 'query'}
}, {
arg: 'id',
type: 'Integer',
description: 'Item id',
http: {source: 'query'}
}, {
arg: 'categoryFk',
type: 'Integer',
description: 'Category id',
http: {source: 'query'}
}, {
arg: 'typeFk',
type: 'Integer',
description: 'Type id',
http: {source: 'query'}
}, {
arg: 'isActive',
type: 'Boolean',
description: 'Whether the the item is o not active',
http: {source: 'query'}
description: 'Whether the the item is or not active',
}, {
arg: 'salesPersonFk',
type: 'Integer',
description: 'The buyer of the item',
http: {source: 'query'}
}, {
arg: 'description',
type: 'String',
description: 'The item description',
}
],
returns: {

View File

@ -122,6 +122,9 @@
"mysql": {
"columnName": "expenceFk"
}
},
"minPrice": {
"type": "number"
}
},
"relations": {

View File

@ -33,13 +33,13 @@
"retAccount": {
"type": "Number"
},
"commision": {
"commission": {
"type": "Boolean"
},
"created": {
"type": "Date"
},
"poscodeFk": {
"postcodeFk": {
"type": "Number"
},
"isActive": {

View File

@ -8,24 +8,24 @@
<vn-table
model="model"
show-fields="$ctrl.showFields"
vn-uvc="itemIndex">
vn-smart-table="itemIndex">
<vn-thead>
<vn-tr>
<vn-th th-id="picture" shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th field="id" shrink>Id</vn-th>
<vn-th th-id="grouping" shrink>Grouping</vn-th>
<vn-th th-id="packing" shrink>Packing</vn-th>
<vn-th th-id="description" style="text-align: center">Description</vn-th>
<vn-th th-id="stems" shrink>Stems</vn-th>
<vn-th th-id="size" shrink>Size</vn-th>
<vn-th th-id="niche" shrink>Niche</vn-th>
<vn-th th-id="type" shrink>Type</vn-th>
<vn-th th-id="category" shrink>Category</vn-th>
<vn-th th-id="intrastat" shrink>Intrastat</vn-th>
<vn-th th-id="origin" shrink>Origin</vn-th>
<vn-th th-id="salesperson" shrink>Buyer</vn-th>
<vn-th th-id="density" shrink>Density</vn-th>
<vn-th th-id="active" shrink>Active</vn-th>
<vn-th field="grouping" shrink>Grouping</vn-th>
<vn-th field="packing" shrink>Packing</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="stems" shrink>Stems</vn-th>
<vn-th field="size" shrink>Size</vn-th>
<vn-th field="niche" shrink>Niche</vn-th>
<vn-th field="type" shrink>Type</vn-th>
<vn-th field="category" shrink>Category</vn-th>
<vn-th field="intrastat" shrink>Intrastat</vn-th>
<vn-th field="origin" shrink>Origin</vn-th>
<vn-th field="salesperson" shrink>Buyer</vn-th>
<vn-th field="density" shrink>Density</vn-th>
<vn-th field="active" shrink>Active</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>

View File

@ -79,10 +79,9 @@
tooltip-position="left">
</vn-button>
<a ui-sref="route.create">
<a ui-sref="route.create" vn-bind="+">
<vn-button class="round md vn-mb-sm"
icon="add"
vn-bind="+"
vn-tooltip="New route"
tooltip-position="left">
</vn-button>

View File

@ -245,7 +245,7 @@ module.exports = Self => {
SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped
FROM tmp.filter f
LEFT JOIN alertLevel al ON al.alertLevel = f.alertLevel
WHERE (f.alertLevelCode = 'FREE' OR f.alertLevel IS NULL)
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
AND f.shipped >= CURDATE()`);
stmts.push('CALL ticketGetProblems()');

View File

@ -70,7 +70,7 @@ describe('Ticket Component vnTicketDescriptor', () => {
window.open = jasmine.createSpy('open');
const params = {
clientId: ticket.client.id,
recipientId: ticket.client.id,
ticketId: ticket.id
};
controller.showDeliveryNote();
@ -85,7 +85,7 @@ describe('Ticket Component vnTicketDescriptor', () => {
const params = {
recipient: ticket.client.email,
clientId: ticket.client.id,
recipientId: ticket.client.id,
ticketId: ticket.id
};
controller.sendDeliveryNote();
@ -135,7 +135,7 @@ describe('Ticket Component vnTicketDescriptor', () => {
});
describe('canStowaway()', () => {
fit('should make a query and return if the ticket can be stowawayed', () => {
it('should make a query and return if the ticket can be stowawayed', () => {
controller.canStowaway();
$httpBackend.flush();
@ -179,13 +179,10 @@ describe('Ticket Component vnTicketDescriptor', () => {
describe('loadData()', () => {
it(`should perform a get query to store the ticket data into the controller`, () => {
controller.ticket = null;
$httpBackend.expectRoute('GET', `Tickets/${ticket.id}`).respond(ticket);
controller.id = ticket.id;
$httpBackend.when('GET', `Tickets/${ticket.id}/isEditable`).respond();
$httpBackend.expectRoute('GET', `Tickets/${ticket.id}`).respond();
controller.loadData();
$httpBackend.flush();
expect(controller.ticket).toEqual(ticket);
});
});
});

View File

@ -143,10 +143,9 @@
vn-tooltip="Payment on account..."
tooltip-position="left">
</vn-button>
<a ui-sref="ticket.create">
<a ui-sref="ticket.create" vn-bind="+">
<vn-button class="round md vn-mb-sm"
icon="add"
vn-bind="+"
vn-tooltip="New ticket"
tooltip-position="left">
</vn-button>

View File

@ -112,7 +112,8 @@ module.exports = Self => {
let stmts = [];
let stmt;
stmt = new ParameterizedSQL(
`SELECT
`SELECT * FROM
(SELECT
t.id,
t.shipped,
t.landed,
@ -132,7 +133,7 @@ module.exports = Self => {
FROM vn.travel t
JOIN vn.agencyMode am ON am.id = t.agencyFk
JOIN vn.warehouse win ON win.id = t.warehouseInFk
JOIN vn.warehouse wout ON wout.id = t.warehouseOutFk`
JOIN vn.warehouse wout ON wout.id = t.warehouseOutFk) AS t`
);
stmt.merge(conn.makeSuffix(filter));

View File

@ -91,7 +91,7 @@ class Controller extends Section {
if (data.holidays) {
data.holidays.forEach(holiday => {
const holidayDetail = holiday.detail && holiday.detail.description;
const holidayDetail = holiday.detail && holiday.detail.name;
const holidayType = holiday.type && holiday.type.name;
const holidayName = holidayDetail || holidayType;

View File

@ -81,8 +81,8 @@ describe('Worker', () => {
$httpBackend.whenRoute('GET', 'Calendars/absences')
.respond({
holidays: [
{dated: today, detail: {description: 'New year'}},
{dated: tomorrow, detail: {description: 'Easter'}}
{dated: today, detail: {name: 'New year'}},
{dated: tomorrow, detail: {name: 'Easter'}}
],
absences: [
{dated: today, absenceType: {name: 'Holiday', rgb: '#aaa'}},

View File

@ -4,42 +4,179 @@ const smtp = require('../core/smtp');
const config = require('../core/config');
module.exports = app => {
app.get('/api/closure/by-ticket', async function(req, res) {
app.get('/api/closure/all', async function(req, res, next) {
try {
res.status(200).json({
message: 'Task executed successfully'
});
await db.rawSql(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close`);
await db.rawSql(`
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
SELECT
t.id AS ticketFk
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.alertLevel = ts.alertLevel
WHERE al.code = 'PACKED'
AND DATE(t.shipped) BETWEEN DATE_ADD(CURDATE(), INTERVAL -2 DAY) AND CURDATE()
AND t.refFk IS NULL
GROUP BY e.ticketFk)`);
await closeAll(req.args);
await db.rawSql(`
UPDATE ticket t
JOIN ticketState ts ON t.id = ts.ticketFk
JOIN alertLevel al ON al.alertLevel = ts.alertLevel
JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
JOIN zone z ON z.id = t.zoneFk
SET t.routeFk = NULL
WHERE shipped BETWEEN CURDATE() AND util.dayEnd(CURDATE())
AND al.code NOT IN('DELIVERED','PACKED')
AND t.routeFk
AND z.name LIKE '%MADRID%'`);
} catch (error) {
next(error);
}
});
app.get('/api/closure/all', async function(req, res) {
res.status(200).json({
message: 'Task executed successfully'
});
app.get('/api/closure/by-ticket', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
res.status(200).json({
message: 'Task executed successfully'
});
await db.rawSql(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close`);
await db.rawSql(`
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
SELECT
t.id AS ticketFk
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.alertLevel = ts.alertLevel
WHERE al.code = 'PACKED'
AND t.id = :ticketId
AND t.refFk IS NULL
GROUP BY e.ticketFk)`, {
ticketId: reqArgs.ticketId
});
await closeAll(reqArgs);
} catch (error) {
next(error);
}
});
app.get('/api/closure/by-agency', async function(req, res) {
try {
const reqArgs = req.args;
if (!reqArgs.agencyModeId)
throw new Error('The argument agencyModeId is required');
if (!reqArgs.warehouseId)
throw new Error('The argument warehouseId is required');
if (!reqArgs.to)
throw new Error('The argument to is required');
res.status(200).json({
message: 'Task executed successfully'
});
await db.rawSql(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close`);
await db.rawSql(`
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
SELECT
t.id AS ticketFk
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.alertLevel = ts.alertLevel
WHERE al.code = 'PACKED'
AND t.agencyModeFk = :agencyModeId
AND t.warehouseFk = :warehouseId
AND DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY) AND :to
AND t.refFk IS NULL
GROUP BY e.ticketFk)`, {
agencyModeId: reqArgs.agencyModeId,
warehouseId: reqArgs.warehouseId,
to: reqArgs.to
});
await closeAll(reqArgs);
} catch (error) {
next(error);
}
});
app.get('/api/closure/by-route', async function(req, res) {
try {
const reqArgs = req.args;
if (!reqArgs.routeId)
throw new Error('The argument routeId is required');
res.status(200).json({
message: 'Task executed successfully'
});
await db.rawSql(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close`);
await db.rawSql(`
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
SELECT
t.id AS ticketFk
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.alertLevel = ts.alertLevel
WHERE al.code = 'PACKED'
AND t.routeFk = :routeId
AND t.refFk IS NULL
GROUP BY e.ticketFk)`, {
routeId: reqArgs.routeId
});
await closeAll(reqArgs);
} catch (error) {
next(error);
}
});
async function closeAll(reqArgs) {
const failedtickets = [];
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.email recipient,
c.isToBeMailed,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
FROM tmp.ticket_close tt
JOIN ticket t ON t.id = tt.ticketFk
JOIN client c ON c.id = t.clientFk
JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.alertLevel = ts.alertLevel
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND DATE(t.shipped) BETWEEN DATE_ADD(CURDATE(), INTERVAL -2 DAY) AND CURDATE()
AND t.refFk IS NULL
GROUP BY e.ticketFk`);
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk`);
for (const ticket of tickets) {
try {
await db.rawSql(`CALL vn.ticket_closeByTicket(:ticketId)`, {
await db.rawSql(`CALL vn.ticket_close(:ticketId)`, {
ticketId: ticket.id
});
if (!ticket.salesPersonFk || !ticket.isToBeMailed) continue;
const hasToInvoice = ticket.hasToInvoice && ticket.hasDailyInvoice;
if (!ticket.salesPersonFk || !ticket.isToBeMailed || hasToInvoice) continue;
if (!ticket.recipient) {
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
@ -54,7 +191,6 @@ module.exports = app => {
continue;
}
const reqArgs = req.args;
const args = Object.assign({
ticketId: ticket.id,
recipientId: ticket.clientFk,
@ -88,5 +224,7 @@ module.exports = app => {
html: body
});
}
});
await db.rawSql(`DROP TEMPORARY TABLE tmp.ticket_close`);
}
};

View File

@ -1,5 +1,5 @@
subject: Your delivery note
title: "Here is your delivery note!"
title: Your delivery note
dear: Dear client
description: The delivery note from the order <strong>{0}</strong> is now available. <br/>
You can download it by clicking <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">this link</a>.

View File

@ -1,5 +1,5 @@
subject: Aquí tienes tu albarán
title: "Aquí tienes tu albarán"
subject: Tu albarán
title: Tu albarán
dear: Estimado cliente
description: Ya está disponible el albarán correspondiente al pedido <strong>{0}</strong>. <br/>
Puedes verlo haciendo clic <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">en este enlace</a>.

View File

@ -1,5 +1,5 @@
subject: Voici votre bon de livraison
title: "Voici votre bon de livraison"
subject: Votre bon de livraison
title: Votre bon de livraison
dear: Cher client,
description: Le bon de livraison correspondant à la commande <strong>{0}</strong> est maintenant disponible.<br/>
Vous pouvez le voir en cliquant <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}" target="_blank">sur ce lien</a>.

View File

@ -1,5 +1,5 @@
subject: Vossa Nota de Entrega
title: "Esta é vossa nota de entrega!"
subject: Vossa nota de entrega
title: Vossa nota de entrega
dear: Estimado cliente
description: Já está disponível sua nota de entrega correspondente a encomenda numero <strong>{0}</strong>. <br/>
Para ver-lo faça um clique <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">neste link</a>.

View File

@ -1,5 +1,5 @@
subject: Your delivery note
title: "Here is your delivery note!"
title: Your delivery note
dear: Dear client
description: The delivery note from the order <strong>{0}</strong> is now available. <br/>
You can download it by clicking on the attachment of this email.

View File

@ -1,5 +1,5 @@
subject: Aquí tienes tu albarán
title: "¡Este es tu albarán!"
subject: Tu albarán
title: Tu albarán
dear: Estimado cliente
description: Ya está disponible el albarán correspondiente al pedido {0}. <br/>
Puedes descargarlo haciendo clic en el adjunto de este correo.

View File

@ -1,5 +1,5 @@
subject: Voici votre bon de livraison
title: "Voici votre bon de livraison!"
subject: Votre bon de livraison
title: Votre bon de livraison
dear: Cher client,
description: Le bon de livraison correspondant à la commande {0} est maintenant disponible.<br/>
Vous pouvez le télécharger en cliquant sur la pièce jointe dans cet email.

View File

@ -1,5 +1,5 @@
subject: Vossa Nota de Entrega
title: "Esta é vossa nota de entrega!"
subject: Vossa nota de entrega
title: Vossa nota de entrega
dear: Estimado cliente
description: Já está disponível sua nota de entrega correspondente a encomenda {0}. <br/>
Podes descarregar-la fazendo um clique no arquivo anexado ao e-mail.