diff --git a/db/changes/232401/00-buyConfig_travelConfig.sql b/db/changes/232401/00-buyConfig_travelConfig.sql new file mode 100644 index 000000000..0f73ddc8c --- /dev/null +++ b/db/changes/232401/00-buyConfig_travelConfig.sql @@ -0,0 +1,28 @@ +CREATE TABLE `vn`.`buyConfig` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `monthsAgo` int(11) NOT NULL DEFAULT 6 COMMENT 'Meses desde la última compra', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +CREATE TABLE `vn`.`travelConfig` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `warehouseInFk` smallint(6) unsigned NOT NULL DEFAULT 8 COMMENT 'Warehouse de origen', + `warehouseOutFk` smallint(6) unsigned NOT NULL DEFAULT 60 COMMENT 'Warehouse destino', + `agencyFk` int(11) NOT NULL DEFAULT 1378 COMMENT 'Agencia por defecto', + `companyFk` smallint(5) unsigned NOT NULL DEFAULT 442 COMMENT 'Compañía por defecto', + PRIMARY KEY (`id`), + KEY `travelConfig_FK` (`warehouseInFk`), + KEY `travelConfig_FK_1` (`warehouseOutFk`), + KEY `travelConfig_FK_2` (`agencyFk`), + KEY `travelConfig_FK_3` (`companyFk`), + CONSTRAINT `travelConfig_FK` FOREIGN KEY (`warehouseInFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_1` FOREIGN KEY (`warehouseOutFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_2` FOREIGN KEY (`agencyFk`) REFERENCES `agencyMode` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_3` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Entry', 'addFromPackaging', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Entry', 'addFromBuy', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Supplier', 'getItemsPackaging', 'READ', 'ALLOW', 'ROLE', 'production'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 15fa96a79..279de67ae 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -905,7 +905,7 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`) INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`) VALUES - (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15,3), + (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'EMB', 0, NULL, 'V', 0, 15,3), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL), @@ -2886,6 +2886,10 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`) (2, 1, 50, 2), (3, 1, 0, 3); +INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`) + VALUES + (1, 1, 1, 1, 442); - - +INSERT INTO `vn`.`buyConfig` (`id`, `monthsAgo`) + VALUES + (1, 6); diff --git a/modules/entry/back/methods/entry/addFromBuy.js b/modules/entry/back/methods/entry/addFromBuy.js new file mode 100644 index 000000000..a40dd3412 --- /dev/null +++ b/modules/entry/back/methods/entry/addFromBuy.js @@ -0,0 +1,107 @@ + +module.exports = Self => { + Self.remoteMethodCtx('addFromBuy', { + description: 'Modify a field of a buy or creates a new one with default values', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The entry id', + http: {source: 'path'} + }, { + arg: 'item', + type: 'number', + required: true, + description: 'The item id', + }, { + arg: 'printedStickers', + type: 'number', + required: true, + description: 'The field to modify', + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/:id/addFromBuy`, + verb: 'POST' + } + }); + + Self.addFromBuy = async(ctx, options) => { + const args = ctx.args; + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {userId}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + let buy = await models.Buy.findOne({where: {entryFk: args.id}}, myOptions); + if (buy) + await buy.updateAttribute('printedStickers', args.printedStickers); + else { + const userConfig = await models.UserConfig.findById(userId, {fields: ['warehouseFk']}, myOptions); + await Self.rawSql( + 'CALL vn.buyUltimate(?,?)', + [userConfig.warehouseFk, null], + myOptions + ); + let buyUltimate = await Self.rawSql( + `SELECT buyFk + FROM tmp.buyUltimate + WHERE itemFk = ?`, + [args.item], + myOptions + ); + buyUltimate = await models.Buy.findById(buyUltimate[0].buyFk, null, myOptions); + buy = await models.Buy.create({ + entryFk: args.id, + itemFk: args.item, + quantity: 0, + dispatched: buyUltimate.dispatched, + buyingValue: buyUltimate.buyingValue, + freightValue: buyUltimate.freightValue, + isIgnored: buyUltimate.isIgnored, + stickers: buyUltimate.stickers, + packing: buyUltimate.packing, + grouping: buyUltimate.grouping, + groupingMode: buyUltimate.groupingMode, + containerFk: buyUltimate.containerFk, + comissionValue: buyUltimate.comissionValue, + packageValue: buyUltimate.packageValue, + location: buyUltimate.location, + packageFk: buyUltimate.packageFk, + price1: buyUltimate.price1, + price2: buyUltimate.price2, + price3: buyUltimate.price3, + minPrice: buyUltimate.minPrice, + printedStickers: args.printedStickers, + workerFk: buyUltimate.workerFk, + isChecked: buyUltimate.isChecked, + isPickedOff: buyUltimate.isPickedOff, + created: buyUltimate.created, + ektFk: buyUltimate.ektFk, + weight: buyUltimate.weight, + deliveryFk: buyUltimate.deliveryFk, + itemOriginalFk: buyUltimate.itemOriginalFk + }, myOptions); + } + + if (tx) await tx.commit(); + return buy; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/entry/back/methods/entry/addFromPackaging.js b/modules/entry/back/methods/entry/addFromPackaging.js new file mode 100644 index 000000000..9ba855303 --- /dev/null +++ b/modules/entry/back/methods/entry/addFromPackaging.js @@ -0,0 +1,72 @@ +module.exports = Self => { + Self.remoteMethodCtx('addFromPackaging', { + description: 'Create a receipt or return entry for a supplier with a specific travel', + accessType: 'WRITE', + accepts: [{ + arg: 'supplier', + type: 'number', + required: true, + description: 'The supplier id', + }, + { + arg: 'isTravelReception', + type: 'boolean', + required: true, + description: 'Indicates if the travel associated with the entry is a return or receipt travel' + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/addFromPackaging`, + verb: 'POST' + } + }); + + Self.addFromPackaging = async(ctx, options) => { + const args = ctx.args; + const models = Self.app.models; + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const travelConfig = await models.TravelConfig.findOne({}, myOptions); + + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + const tomorrow = new Date(today); + tomorrow.setDate(today.getDate() + 1); + + const travel = await models.Travel.create({ + shipped: args.isTravelReception ? yesterday : today, + landed: args.isTravelReception ? today : tomorrow, + agencyModeFk: travelConfig.agencyFk, + warehouseInFk: travelConfig.warehouseOutFk, + warehouseOutFk: travelConfig.warehouseInFk + }, myOptions); + + const entry = await models.Entry.create({ + supplierFk: args.supplier, + travelFk: travel.id, + companyFk: travelConfig.companyFk + }, myOptions); + + if (tx) await tx.commit(); + + return entry; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/entry/back/methods/entry/specs/addFromBuy.spec.js b/modules/entry/back/methods/entry/specs/addFromBuy.spec.js new file mode 100644 index 000000000..b6ed475b3 --- /dev/null +++ b/modules/entry/back/methods/entry/specs/addFromBuy.spec.js @@ -0,0 +1,51 @@ +const models = require('vn-loopback/server/server').models; + +describe('entry addFromBuy()', () => { + const ctx = {req: {accessToken: {userId: 18}}}; + + it('should change the printedStickers of an existent buy', async() => { + const id = 1; + const item = 1; + const buy = 1; + + const tx = await models.Entry.beginTransaction({}); + const options = {transaction: tx}; + try { + const currentBuy = await models.Buy.findById(buy, {fields: ['printedStickers']}, options); + const printedStickers = currentBuy.printedStickers + 10; + ctx.args = {id, item, printedStickers}; + const newBuy = await models.Entry.addFromBuy(ctx, options); + + expect(newBuy.printedStickers).toEqual(printedStickers); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should create for an entry without a concrete item a new buy', async() => { + const id = 8; + const item = 1; + const printedStickers = 10; + + const tx = await models.Entry.beginTransaction({}); + const options = {transaction: tx}; + try { + const emptyBuy = await models.Buy.findOne({where: {entryFk: id}}, options); + ctx.args = {id, item, printedStickers}; + const newBuy = await models.Entry.addFromBuy(ctx, options); + + expect(emptyBuy).toEqual(null); + expect(newBuy.entryFk).toEqual(id); + expect(newBuy.printedStickers).toEqual(printedStickers); + expect(newBuy.itemFk).toEqual(item); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/entry/back/methods/entry/specs/addFromPackaging.spec.js b/modules/entry/back/methods/entry/specs/addFromPackaging.spec.js new file mode 100644 index 000000000..1b0d4656f --- /dev/null +++ b/modules/entry/back/methods/entry/specs/addFromPackaging.spec.js @@ -0,0 +1,49 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('entry addFromPackaging()', () => { + const supplier = 442; + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + + beforeAll(async() => { + const activeCtx = { + accessToken: {userId: 49}, + http: { + req: { + headers: {origin: 'http://localhost'}, + }, + }, + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx, + }); + }); + + it('should create an incoming travel', async() => { + const ctx = {args: {isTravelReception: true, supplier}}; + const tx = await models.Entry.beginTransaction({}); + const options = {transaction: tx}; + + try { + const entry = await models.Entry.addFromPackaging(ctx, options); + const travelConfig = await models.TravelConfig.findOne({}, options); + const travel = await models.Travel.findOne({order: 'id DESC'}, options); + + expect(new Date(travel.shipped).getDate()).toEqual(yesterday.getDate()); + expect(new Date(travel.landed).getDate()).toEqual(today.getDate()); + expect(travel.agencyModeFk).toEqual(travelConfig.agencyFk); + expect(travel.warehouseInFk).toEqual(travelConfig.warehouseOutFk); + expect(travel.warehouseOutFk).toEqual(travelConfig.warehouseInFk); + expect(entry.supplierFk).toEqual(supplier); + expect(entry.travelFk).toEqual(travel.id); + expect(entry.companyFk).toEqual(travelConfig.companyFk); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/entry/back/model-config.json b/modules/entry/back/model-config.json index ad5a9063e..ca4472c8c 100644 --- a/modules/entry/back/model-config.json +++ b/modules/entry/back/model-config.json @@ -5,6 +5,9 @@ "Buy": { "dataSource": "vn" }, + "BuyConfig": { + "dataSource": "vn" + }, "ItemMatchProperties": { "dataSource": "vn" }, diff --git a/modules/entry/back/models/buy-config.json b/modules/entry/back/models/buy-config.json new file mode 100644 index 000000000..f48fec22a --- /dev/null +++ b/modules/entry/back/models/buy-config.json @@ -0,0 +1,18 @@ +{ + "name": "BuyConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "buyConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "showLastBuy": { + "type": "number" + } + } +} diff --git a/modules/entry/back/models/buy.json b/modules/entry/back/models/buy.json index 379e55427..af205533f 100644 --- a/modules/entry/back/models/buy.json +++ b/modules/entry/back/models/buy.json @@ -39,6 +39,9 @@ "packageValue": { "type": "number" }, + "price1": { + "type": "number" + }, "price2": { "type": "number" }, @@ -47,7 +50,44 @@ }, "weight": { "type": "number" + }, + "printedStickers": { + "type": "number" + }, + "dispatched": { + "type": "number" + }, + "isIgnored": { + "type": "boolean" + }, + "containerFk": { + "type": "number" + }, + "location": { + "type": "number" + }, + "minPrice": { + "type": "number" + }, + "isChecked": { + "type": "boolean" + }, + "isPickedOff": { + "type": "boolean" + }, + "created": { + "type": "date" + }, + "ektFk": { + "type": "number" + }, + "itemOriginalFk": { + "type": "number" + }, + "editorFk": { + "type": "number" } + }, "relations": { "entry": { @@ -64,6 +104,16 @@ "type": "belongsTo", "model": "Packaging", "foreignKey": "packageFk" + }, + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "workerFk" + }, + "delivery": { + "type": "belongsTo", + "model": "Delivery", + "foreignKey": "deliveryFk" } } } diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js index 1980f964c..0eabd70ee 100644 --- a/modules/entry/back/models/entry.js +++ b/modules/entry/back/models/entry.js @@ -8,6 +8,8 @@ module.exports = Self => { require('../methods/entry/importBuysPreview')(Self); require('../methods/entry/lastItemBuys')(Self); require('../methods/entry/entryOrderPdf')(Self); + require('../methods/entry/addFromPackaging')(Self); + require('../methods/entry/addFromBuy')(Self); Self.observe('before save', async function(ctx, options) { if (ctx.isNewInstance) return; diff --git a/modules/supplier/back/methods/supplier/getItemsPackaging.js b/modules/supplier/back/methods/supplier/getItemsPackaging.js new file mode 100644 index 000000000..8ef80529b --- /dev/null +++ b/modules/supplier/back/methods/supplier/getItemsPackaging.js @@ -0,0 +1,50 @@ +module.exports = Self => { + Self.remoteMethod('getItemsPackaging', { + description: 'Returns the list of items from the supplier of type packing', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The supplier id', + http: {source: 'path'} + }, { + arg: 'entry', + type: 'number', + required: true, + description: 'The entry id', + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/:id/getItemsPackaging`, + verb: 'GET' + } + }); + Self.getItemsPackaging = async(id, entry) => { + return Self.rawSql(` + WITH entryTmp AS ( + SELECT i.id, SUM(b.quantity) quantity + FROM vn.entry e + JOIN vn.buy b ON b.entryFk = e.id + JOIN vn.supplier s ON s.id = e.supplierFk + JOIN vn.item i ON i.id = b.itemFk + WHERE e.id = ? AND e.supplierFk = ? + GROUP BY i.id + ) SELECT i.id, i.name, et.quantity, SUM(b.quantity) quantityTotal + FROM vn.buy b + JOIN vn.item i ON i.id = b.itemFk + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.supplier s ON s.id = e.supplierFk + JOIN vn.buyConfig bc ON bc.monthsAgo + JOIN vn.travel t ON t.id = e.travelFk + LEFT JOIN entryTmp et ON et.id = i.id + WHERE e.supplierFk = ? + AND i.family IN ('EMB', 'CONT') + AND b.created > (util.VN_CURDATE() - INTERVAL bc.monthsAgo MONTH) + GROUP BY b.itemFk + ORDER BY et.quantity DESC, quantityTotal DESC`, [entry, id, id]); + }; +}; diff --git a/modules/supplier/back/methods/supplier/specs/getItemsPackaging.spec.js b/modules/supplier/back/methods/supplier/specs/getItemsPackaging.spec.js new file mode 100644 index 000000000..8e4cc9145 --- /dev/null +++ b/modules/supplier/back/methods/supplier/specs/getItemsPackaging.spec.js @@ -0,0 +1,12 @@ +const app = require('vn-loopback/server/server'); + +describe('Supplier getItemsPackaging()', () => { + it('should return a summary of the list of items from a specific supplier', async() => { + const [item] = await app.models.Supplier.getItemsPackaging(1, 1); + + expect(item.id).toEqual(1); + expect(item.name).toEqual('Ranged weapon longbow 2m'); + expect(item.quantity).toEqual(5000); + expect(item.quantityTotal).toEqual(5100); + }); +}); diff --git a/modules/supplier/back/models/supplier.js b/modules/supplier/back/models/supplier.js index e113e5d59..9c78e8590 100644 --- a/modules/supplier/back/models/supplier.js +++ b/modules/supplier/back/models/supplier.js @@ -11,6 +11,7 @@ module.exports = Self => { require('../methods/supplier/campaignMetricsPdf')(Self); require('../methods/supplier/campaignMetricsEmail')(Self); require('../methods/supplier/newSupplier')(Self); + require('../methods/supplier/getItemsPackaging')(Self); Self.validatesPresenceOf('name', { message: 'The social name cannot be empty' diff --git a/modules/travel/back/model-config.json b/modules/travel/back/model-config.json index 34321ba78..ed5c071b3 100644 --- a/modules/travel/back/model-config.json +++ b/modules/travel/back/model-config.json @@ -14,6 +14,9 @@ "TravelThermograph": { "dataSource": "vn" }, + "TravelConfig": { + "dataSource": "vn" + }, "Temperature": { "dataSource": "vn" } diff --git a/modules/travel/back/models/travel-config.json b/modules/travel/back/models/travel-config.json new file mode 100644 index 000000000..360c0b81a --- /dev/null +++ b/modules/travel/back/models/travel-config.json @@ -0,0 +1,49 @@ +{ + "name": "TravelConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "travelConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + }, + "warehouseInFk": { + "type": "number" + }, + "warehouseOutFk": { + "type": "number" + }, + "agencyFk": { + "type": "number" + }, + "companyFk": { + "type": "number" + } + }, + "relations": { + "warehouseIn": { + "type": "belongsTo", + "model": "Warehouse", + "foreignKey": "warehouseInFk" + }, + "warehouseOut": { + "type": "belongsTo", + "model": "Warehouse", + "foreignKey": "warehouseOutFk" + }, + "agency": { + "type": "belongsTo", + "model": "AgencyMode", + "foreignKey": "agencyFk" + }, + "company": { + "type": "belongsTo", + "model": "Company", + "foreignKey": "companyFk" + } + } +}