Merchant.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. class MerchantIngredient{
  2. constructor(ingredient, quantity){
  3. if(quantity < 0){
  4. banner.createError("QUANTITY CANNOT BE A NEGATIVE NUMBER");
  5. return false;
  6. }
  7. this._ingredient = ingredient;
  8. this._quantity = this.convertToBase(quantity);
  9. }
  10. get ingredient(){
  11. return this._ingredient;
  12. }
  13. get quantity(){
  14. switch(this._ingredient.unit){
  15. case "g":return this._quantity;
  16. case "kg": return this._quantity * 1000;
  17. case "oz": return this._quantity * 28.3495;
  18. case "lb": return this._quantity * 453.5924;
  19. case "ml": return this._quantity / 1000;
  20. case "l": return this._quantity;
  21. case "tsp": return this._quantity / 202.8842;
  22. case "tbsp": return this._quantity / 67.6278;
  23. case "ozfl": return this._quantity / 33.8141;
  24. case "cup": return this._quantity / 4.1667;
  25. case "pt": return this._quantity / 2.1134;
  26. case "qt": return this._quantity / 1.0567;
  27. case "gal": return this._quantity * 3.7854;
  28. case "mm": return this._quantity / 1000;
  29. case "cm": return this._quantity / 100;
  30. case "m": return this._quantity;
  31. case "in": return this._quantity / 39.3701;
  32. case "ft": return this._quantity / 3.2808;
  33. default: return this._quantity;
  34. }
  35. }
  36. set quantity(quantity){
  37. if(quantity < 0){
  38. return false;
  39. }
  40. this._quantity = this.convertToBase(quantity);
  41. }
  42. convertToBase(quantity){
  43. switch(this._ingredient.unit){
  44. case "g": return quantity;
  45. case "kg": return quantity / 1000;
  46. case "oz": return quantity / 28.3495;
  47. case "lb": return quantity / 453.5924;
  48. case "ml": return quantity *= 1000;
  49. case "l": return quantity;
  50. case "tsp": return quantity * 202.8842;
  51. case "tbsp": return quantity * 67.6278;
  52. case "ozfl": return quantity * 33.8141;
  53. case "cup": return quantity * 4.1667;
  54. case "pt": return quantity * 2.1134;
  55. case "qt": return quantity * 1.0567;
  56. case "gal": return quantity / 3.7854;
  57. case "mm": return quantity * 1000;
  58. case "cm": return quantity * 100;
  59. case "m": return quantity;
  60. case "in": return quantity * 39.3701;
  61. case "ft": return quantity * 3.2808;
  62. default: return quantity;
  63. }
  64. }
  65. }
  66. class Merchant{
  67. constructor(oldMerchant, transactions, modules){
  68. this._modules = modules;
  69. this._name = oldMerchant.name;
  70. this._pos = oldMerchant.pos;
  71. this._ingredients = [];
  72. this._recipes = [];
  73. this._transactions = [];
  74. this._orders = [];
  75. this._units = {
  76. mass: ["g", "kg", "oz", "lb"],
  77. volume: ["ml", "l", "tsp", "tbsp", "ozfl", "cup", "pt", "qt", "gal"],
  78. length: ["mm", "cm", "m", "in", "ft"],
  79. other: ["each", "bottle"]
  80. }
  81. //populate ingredients
  82. for(let i = 0; i < oldMerchant.inventory.length; i++){
  83. const ingredient = new modules.Ingredient(
  84. oldMerchant.inventory[i].ingredient._id,
  85. oldMerchant.inventory[i].ingredient.name,
  86. oldMerchant.inventory[i].ingredient.category,
  87. oldMerchant.inventory[i].ingredient.unitType,
  88. oldMerchant.inventory[i].defaultUnit,
  89. this,
  90. oldMerchant.inventory[i].ingredient.specialUnit,
  91. oldMerchant.inventory[i].ingredient.unitSize
  92. );
  93. const merchantIngredient = new MerchantIngredient(
  94. ingredient,
  95. oldMerchant.inventory[i].quantity
  96. );
  97. this._ingredients.push(merchantIngredient);
  98. }
  99. //populate recipes
  100. for(let i = 0; i < oldMerchant.recipes.length; i++){
  101. let ingredients = [];
  102. for(let j = 0; j < oldMerchant.recipes[i].ingredients.length; j++){
  103. const ingredient = oldMerchant.recipes[i].ingredients[j];
  104. for(let k = 0; k < this._ingredients.length; k++){
  105. if(ingredient.ingredient === this._ingredients[k].ingredient.id){
  106. ingredients.push({
  107. ingredient: this._ingredients[k].ingredient,
  108. quantity: ingredient.quantity
  109. });
  110. break;
  111. }
  112. }
  113. }
  114. this._recipes.push(new this._modules.Recipe(
  115. oldMerchant.recipes[i]._id,
  116. oldMerchant.recipes[i].name,
  117. oldMerchant.recipes[i].price,
  118. ingredients,
  119. this
  120. ));
  121. }
  122. //populate transactions
  123. for(let i = 0; i < transactions.length; i++){
  124. this._transactions.push(new modules.Transaction(
  125. transactions[i]._id,
  126. transactions[i].date,
  127. transactions[i].recipes,
  128. this
  129. ));
  130. }
  131. }
  132. get modules(){
  133. return this._modules;
  134. }
  135. get name(){
  136. return this._name;
  137. }
  138. set name(name){
  139. if(this.isSanitaryString(name)){
  140. this._name = name;
  141. }
  142. return false;
  143. }
  144. get pos(){
  145. return this._pos;
  146. }
  147. get ingredients(){
  148. return this._ingredients;
  149. }
  150. addIngredient(ingredient, quantity){
  151. const MerchantIngredient = new MerchantIngredient(ingredient, quantity);
  152. this._ingredients.push(merchantIngredient);
  153. this._modules.home.isPopulated = false;
  154. this._modules.ingredients.isPopulated = false;
  155. }
  156. removeIngredient(ingredient){
  157. const index = this._ingredients.indexOf(ingredient);
  158. if(index === undefined){
  159. return false;
  160. }
  161. this._ingredients.splice(index, 1);
  162. this._modules.home.isPopulated = false;
  163. this._modules.ingredients.isPopulated = false;
  164. this._modules.ingredients.display();
  165. controller.closeSidebar();
  166. }
  167. updateIngredient(ingredient, quantity){
  168. const index = this._ingredients.indexOf(ingredient);
  169. if(index === undefined){
  170. return false;
  171. }
  172. this._ingredients[index].quantity = quantity;
  173. this._modules.home.isPopulated = false;
  174. this._modules.ingredients.isPopulated = false;
  175. this._modules.ingredients.display()
  176. controller.closeSidebar();
  177. }
  178. get recipes(){
  179. return this._recipes;
  180. }
  181. addRecipe(recipe){
  182. this._recipes.push(recipe);
  183. this._modules.transactions.isPopulated = false;
  184. this._modules.recipeBook.isPopulated = false;
  185. this._modules.recipeBook.display();
  186. controller.closeSidebar();
  187. }
  188. removeRecipe(recipe){
  189. const index = this._recipes.indexOf(recipe);
  190. if(index === undefined){
  191. return false;
  192. }
  193. this._recipes.splice(index, 1);
  194. this._modules.transactions.isPopulated = false;
  195. this._modules.recipeBook.isPopulated = false;
  196. this._modules.recipeBook.display();
  197. controller.closeSidebar();
  198. }
  199. getTransactions(from = 0, to = new Date()){
  200. if(from === 0){
  201. from = this._transactions[0].date;
  202. }
  203. const {start, end} = this.getTransactionIndices(from, to);
  204. return this._transactions.slice(start, end);
  205. }
  206. addTransaction(transaction){
  207. this._transactions.push(transaction);
  208. this._transactions.sort((a, b)=>{
  209. if(a.date > b.date){
  210. return -1;
  211. }
  212. return 1;
  213. });
  214. let ingredients = {};
  215. for(let i = 0; i < transaction.recipes.length; i++){
  216. const recipe = transaction.recipes[i];
  217. for(let j = 0; j < recipe.recipe.ingredients.length; j++){
  218. const ingredient = recipe.ingredients[i];
  219. if(ingredients[ingredient.ingredient.id]){
  220. ingredients[ingredient.ingredient.id] += recipe.quantity * ingredient.quantity;
  221. }else{
  222. ingredients[ingredient.ingredient.id] = recipe.quantity * ingredient.quantity;
  223. }
  224. }
  225. }
  226. const keys = Object.keys(ingredients);
  227. for(let i = 0; i < keys.length; i++){
  228. for(let j = 0; j < this._ingredients.length; j++){
  229. if(keys[i] === this._ingredients[j].ingredient.id){
  230. this._ingredients.quantity -= ingredients[keys[i]];
  231. }
  232. }
  233. }
  234. this._modules.home.isPopulated = false;
  235. this._modules.ingredients.isPopulated = false;
  236. this._modules.transactions.isPopulated = false;
  237. this._modules.analytics.newData = true;
  238. this._modeules.transactions.display();
  239. controller.closeSidebar();
  240. }
  241. removeTransaction(transaction){
  242. const index = this._transactions.indexOf(transaction);
  243. if(index === undefined){
  244. return false;
  245. }
  246. this._transactions.splice(index, 1);
  247. let ingredients = {};
  248. for(let i = 0; i < transaction.recipes.length; i++){
  249. const recipe = transaction.recipes[i];
  250. for(let j = 0; j < recipe.recipe.ingredients.length; j++){
  251. const ingredient = recipe.recipe.ingredients[i];
  252. if(ingredients[ingredient.ingredient.id]){
  253. ingredients[ingredient.ingredient.id] += ingredient.quantity * recipe.quantity;
  254. }else{
  255. ingredients[ingredient.ingredient.id] = ingredient.quantity * recipe.quantity;
  256. }
  257. }
  258. }
  259. const keys = Object.keys(ingredients);
  260. for(let i = 0; i < keys.length; i++){
  261. for(let j = 0; j < this._ingredients.length; j++){
  262. if(keys[i] === this._ingredients[j].ingredient.id){
  263. this._ingredients.quantity += ingredients[keys[i]];
  264. }
  265. }
  266. }
  267. this._modules.home.isPopulated = false;
  268. this._modules.ingredients.isPopulated = false;
  269. this._modules.transactions.isPopulated = false;
  270. this._modules.analytics.newData = true;
  271. this._modules.transactions.display();
  272. controller.closeSidebar();
  273. }
  274. get orders(){
  275. return this._orders;
  276. }
  277. addOrder(order, isNew = false){
  278. this._orders.push(order);
  279. if(isNew){
  280. for(let i = 0; i < order.ingredients.length; i++){
  281. for(let j = 0; j < this._ingredients.length; j++){
  282. if(order.ingredients[i] === this._ingredients[j].ingredient){
  283. this._ingredients[j].quantity += order.ingredients[i].quantity;
  284. }
  285. }
  286. }
  287. }
  288. this._modules.ingredients.isPopulated = false;
  289. this._modules.orders.isPopulated = false;
  290. this._modules.orders.display();
  291. controller.closeSidebar();
  292. }
  293. removeOrder(order){
  294. const index = this._orders.indexOf(order);
  295. if(index === undefined){
  296. return false;
  297. }
  298. this._orders.splice(index, 1);
  299. for(let i = 0; i < order.ingredients.length; i++){
  300. for(let j = 0; j < this._ingredients.length; j++){
  301. if(order.ingredients[i].ingredient === this._ingredients[j].ingredient){
  302. this._ingredients[j].quantity -= order.ingredients[i].quantity;
  303. }
  304. }
  305. }
  306. this._modules.ingredients.isPopulated = false;
  307. this._modules.orders.isPopulated = false;
  308. this._modules.orders.display();
  309. controller.closeSidebar();
  310. }
  311. get units(){
  312. return this._units;
  313. }
  314. getRevenue(from, to = new Date()){
  315. if(from === 0){
  316. from = this._transactions[0].date;
  317. }
  318. const {start, end} = this.getTransactionIndices(from, to);
  319. let total = 0;
  320. for(let i = start; i <= end; i++){
  321. for(let j = 0; j < this._transactions[i].recipes.length; j++){
  322. for(let k = 0; k < this.recipes.length; k++){
  323. if(this._transactions[i].recipes[j].recipe === this.recipes[k]){
  324. total += this._transactions[i].recipes[j].quantity * this.recipes[k].price;
  325. }
  326. }
  327. }
  328. }
  329. return total / 100;
  330. }
  331. /*
  332. Gets the quantity of each ingredient sold between two dates (dateRange)
  333. Inputs
  334. dateRange: list containing a start date and an end date
  335. Return:
  336. [{
  337. ingredient: Ingredient object,
  338. quantity: quantity of ingredient sold
  339. }]
  340. */
  341. getIngredientsSold(from = 0, to = new Date()){
  342. if(from = 0){
  343. from = this._ingredients[0].date;
  344. }
  345. let recipes = this.getRecipesSold(from, to);
  346. let ingredientList = [];
  347. for(let i = 0; i < recipes.length; i++){
  348. for(let j = 0; j < recipes[i].recipe.ingredients.length; j++){
  349. let exists = false;
  350. for(let k = 0; k < ingredientList.length; k++){
  351. if(ingredientList[k].ingredient === recipes[i].recipe.ingredients[j].ingredient){
  352. exists = true;
  353. ingredientList[k].quantity += recipes[i].quantity * recipes[i].recipe.ingredients[j].quantity;
  354. }
  355. }
  356. if(!exists){
  357. ingredientList.push({
  358. ingredient: recipes[i].recipe.ingredients[j].ingredient,
  359. quantity: recipes[i].quantity * recipes[i].recipe.ingredients[j].quantity
  360. });
  361. }
  362. }
  363. }
  364. return ingredientList;
  365. }
  366. getSingleIngredientSold(ingredient, from = 0, to = new Date()){
  367. if(from === 0){
  368. from = this._transactions[0].date;
  369. }
  370. const {start, end} = this.getTransactionIndices(from, to);
  371. let total = 0;
  372. for(let i = start; i < end; i++){
  373. for(let j = 0; j < this._transactions[i].recipes.length; j++){
  374. for(let k = 0; k < this._transactions[i].recipes[j].recipe.ingredients.length; k++){
  375. if(this._transactions[i].recipes[j].recipe.ingredients[k].ingredient === ingredient.ingredient){
  376. total += this._transactions[i].recipes[j].recipe.ingredients[k].quantity;
  377. }
  378. }
  379. }
  380. }
  381. return total;
  382. }
  383. /*
  384. Gets the number of recipes sold between two dates (dateRange)
  385. Inputs:
  386. dateRange: array containing a start date and an end date
  387. Return:
  388. [{
  389. recipe: a recipe object
  390. quantity: quantity of the recipe sold
  391. }]
  392. */
  393. getRecipesSold(from = 0, to = new Date()){
  394. if(from = 0){
  395. from = this._transactions[0].date;
  396. }
  397. const {start, end} = this.getTransactionIndices(from, to);
  398. let recipeList = [];
  399. for(let i = start; i <= end; i++){
  400. for(let j = 0; j < this._transactions[i].recipes.length; j++){
  401. let exists = false;
  402. for(let k = 0; k < recipeList.length; k++){
  403. if(recipeList[k].recipe === this._transactions[i].recipes[j].recipe){
  404. exists = true;
  405. recipeList[k].quantity += this._transactions[i].recipes[j].quantity;
  406. }
  407. }
  408. if(!exists){
  409. recipeList.push({
  410. recipe: this._transactions[i].recipes[j].recipe,
  411. quantity: this._transactions[i].recipes[j].quantity
  412. });
  413. }
  414. }
  415. }
  416. return recipeList;
  417. }
  418. /*
  419. Groups all of the merchant's ingredients by their category
  420. Return: [{
  421. name: category name,
  422. ingredients: [Ingredient Object]
  423. }]
  424. */
  425. categorizeIngredients(){
  426. let ingredientsByCategory = [];
  427. for(let i = 0; i < this.ingredients.length; i++){
  428. let categoryExists = false;
  429. for(let j = 0; j < ingredientsByCategory.length; j++){
  430. if(this.ingredients[i].ingredient.category === ingredientsByCategory[j].name){
  431. ingredientsByCategory[j].ingredients.push(this.ingredients[i]);
  432. categoryExists = true;
  433. }
  434. }
  435. if(!categoryExists){
  436. ingredientsByCategory.push({
  437. name: this.ingredients[i].ingredient.category,
  438. ingredients: [this.ingredients[i]]
  439. });
  440. }
  441. }
  442. return ingredientsByCategory;
  443. }
  444. unitizeIngredients(){
  445. let ingredientsByUnit = [];
  446. for(let i = 0; i < this.ingredients.length; i++){
  447. let unitExists = false;
  448. const innerIngredient = this.ingredients[i].ingredient;
  449. for(let j = 0; j < ingredientsByUnit.length; j++){
  450. if(innerIngredient.unit === ingredientsByUnit[j].name || innerIngredient.specialUnit === ingredientsByUnit[j].name){
  451. ingredientsByUnit[j].ingredients.push(this.ingredients[i]);
  452. unitExists = true;
  453. }
  454. }
  455. if(!unitExists){
  456. let unit = "";
  457. if(innerIngredient.specialUnit === "bottle"){
  458. unit = "bottle";
  459. }else{
  460. unit = innerIngredient.unit;
  461. }
  462. ingredientsByUnit.push({
  463. name: unit,
  464. ingredients: [this.ingredients[i]]
  465. });
  466. }
  467. }
  468. return ingredientsByUnit;
  469. }
  470. getRecipesForIngredient(ingredient){
  471. let recipes = [];
  472. for(let i = 0; i < this._recipes.length; i++){
  473. for(let j = 0; j < this._recipes[i].ingredients.length; j++){
  474. if(this._recipes[i].ingredients[j].ingredient === ingredient){
  475. recipes.push(this._recipes[i]);
  476. }
  477. }
  478. }
  479. return recipes;
  480. }
  481. getTransactionIndices(from, to){
  482. for(let i = 0; i < this._transactions.length; i++){
  483. if(this._transactions[i].date >= from){
  484. from = i;
  485. }
  486. }
  487. for(let i = this._transactions.length - 1; i >= 0; i--){
  488. if(this._transactions[i].date <= to){
  489. to = i;
  490. }
  491. }
  492. return {from: from, to: to};
  493. }
  494. isSanitaryString(str){
  495. let disallowed = ["\\", "<", ">", "$", "{", "}", "(", ")"];
  496. for(let i = 0; i < disallowed.length; i++){
  497. if(str.includes(disallowed[i])){
  498. return false;
  499. }
  500. }
  501. return true;
  502. }
  503. }
  504. module.exports = Merchant;