From 907c213cda0a59de474e5e466211a9acf578f984 Mon Sep 17 00:00:00 2001 From: Florian Lentsch Date: Fri, 15 Mar 2024 14:06:13 +0100 Subject: [PATCH] Squashed commit of the following: commit 8df48b744fbee9a5557c75782b47dfc4eb6ee18e Author: Florian Lentsch Date: Fri Mar 15 11:36:13 2024 +0100 Another attempt at running rubocop commit a94f05d3062b2c9875f529c7cdc47f8c095caf9b Author: Florian Lentsch Date: Fri Mar 15 11:11:21 2024 +0100 Revert "rubocop -a with upstream rubocop" This reverts commit 9457ec09b254937c98f0e213dd95283b75291f46. commit 9457ec09b254937c98f0e213dd95283b75291f46 Author: Florian Lentsch Date: Fri Mar 15 10:28:12 2024 +0100 rubocop -a with upstream rubocop commit a23f34f4649b051152647664b5df3afdb5d29ce2 Author: Florian Lentsch Date: Fri Mar 1 17:17:27 2024 +0100 Fixes #51 commit 3aa7f503e64e400d41814e16e0abfb3defb9e556 Author: Florian Lentsch Date: Fri Mar 1 13:37:20 2024 +0100 Fixes #52 commit 8a288b8761399e770b173cf8db3705ad9ec57b15 Author: Florian Lentsch Date: Fri Mar 1 12:11:17 2024 +0100 Fixes #48 (another fix) commit 6cb809e4900960fa556e7c8072211cef8b5a9fbd Author: Florian Lentsch Date: Fri Mar 1 11:37:48 2024 +0100 Closes #24 again commit d0f321c36707cc28b619a3fcb88f67c74da23c55 Author: Florian Lentsch Date: Fri Feb 23 15:54:25 2024 +0100 Fixes #43 commit 943f178ac93b3950de98df541914ce5e32ae91e8 Author: Florian Lentsch Date: Fri Feb 23 15:41:08 2024 +0100 On #47: Escape commas in ratio unit names commit 614cfdc0d3e32ce46888a8f12150872dc1f02802 Author: Florian Lentsch Date: Fri Feb 23 14:29:46 2024 +0100 Some more fixes for #44 commit f351deed4d8e1bf1202899b3083268c268da73a5 Author: Florian Lentsch Date: Fri Feb 16 16:16:32 2024 +0100 Some more fixes for #44 commit 34b6a515467f69be11e6c595181d2efc1b8e35a7 Author: Florian Lentsch Date: Sun Feb 11 16:48:25 2024 +0100 Fixes #40 commit 5c124854c990165a760d7d36f703d7434db33390 Author: Florian Lentsch Date: Sun Feb 11 14:13:31 2024 +0100 On #47: Removed unused columns from import/export commit 3341d9d553e3f8a22ce0ce7c7cabf485a33213c4 Author: Florian Lentsch Date: Fri Feb 9 14:56:34 2024 +0100 Closes #31 commit 9b66b289b8b67a32145399da1db84a862cecdd94 Author: Florian Lentsch Date: Mon Feb 5 08:04:38 2024 +0100 Fixes #45 commit da4e1860b8a04faf7b15e4cd2b74b9ccab78e21a Author: Florian Lentsch Date: Sun Feb 4 17:47:38 2024 +0100 Fixes parts of #44: - accounting menu: group order amounts (see posting above) - group order result (of a closed or settled order) - manage order menu: sorted in articles / sorted in groups commit 464449734f02058109c0375668cc8d9a07cd4a5f Author: Florian Lentsch Date: Sun Feb 4 13:56:14 2024 +0100 Fixes #41 see incorrect bugfix for #7 in 34c198ff6e21cfd43cb99d648748d6e1e01199ba commit 97f635a2121c32c3722755494cb3454523f30a5c Author: Florian Lentsch Date: Sat Feb 3 15:14:38 2024 +0100 Fixes #38 commit 9642e9b4f8b53f5559c2a532cd4612354850bb70 Author: Florian Lentsch Date: Sat Feb 3 12:16:19 2024 +0100 Fixes #48 commit 683d2af5485908beaaee598fe378744b86efba9c Author: Florian Lentsch Date: Sun Jan 14 18:09:45 2024 +0100 First draft of #31 commit 88b75288730d06b1b8ac04a5424cd7aa611d1278 Author: Florian Lentsch Date: Fri Jan 12 15:07:51 2024 +0100 Revert "Fixes with upstream rubocop 1.50.2" This reverts commit a5ce332ab736bde86fa474cf7e71caaa83b33ef3. commit a5ce332ab736bde86fa474cf7e71caaa83b33ef3 Author: Florian Lentsch Date: Fri Jan 12 14:45:36 2024 +0100 Fixes with upstream rubocop 1.50.2 commit 4561797f66b62d9b5bdf9503a0d0a4a50a45aa4c Author: Florian Lentsch Date: Fri Jan 12 10:58:45 2024 +0100 Closes #34 commit 5a06392f29249374b46aa8fd88e30462830ea6ca Author: Florian Lentsch Date: Fri Jan 12 10:27:03 2024 +0100 Fixes #33 commit aac0546a5d579d76da869bac7cb5f8487c42f03e Author: Florian Lentsch Date: Fri Jan 12 10:17:26 2024 +0100 Fixes #36 commit dd35ba67b2d004ae94926aa8a739fecabceee9f5 Author: Florian Lentsch Date: Fri Jan 5 09:39:31 2024 +0100 Fixes article import (load extra units) commit ac81c0e12fe130684c9f56e32e6e9f023091288e Author: Florian Lentsch Date: Thu Jan 4 17:27:49 2024 +0100 Fixed bug in import (related to #10) commit 38de81284abaf9c6e9816dd6224848f4e51c7978 Author: Florian Lentsch Date: Thu Jan 4 15:41:04 2024 +0100 Adapted seeds commit d069d3901c9b733da83f9f30ac2083d9d37db1a3 Author: Florian Lentsch Date: Thu Jan 4 11:24:15 2024 +0100 Fixes nasty bug with article unit migrations commit 43897db34b6195347c62b1664b1b912097dbc726 Author: Florian Lentsch Date: Wed Jan 3 20:38:37 2024 +0100 Closes #30 commit 545b368b0df428f83619b73f16b44d2647a047bf Author: Florian Lentsch Date: Wed Jan 3 18:35:01 2024 +0100 Fixes issue in units migration commit 56bfb79610080444461c3a397d60ba812be6648b Author: Florian Lentsch Date: Wed Jan 3 13:34:28 2024 +0100 Fixes bug in order articles controller commit 9a0f813909ca37379465cbe6c114b303fc36917e Author: Florian Lentsch Date: Wed Jan 3 13:20:41 2024 +0100 On #23: Added some translations commit 21740ad6a1c01b5797cb02f5339f625ffa149470 Author: Florian Lentsch Date: Wed Jan 3 11:29:07 2024 +0100 Fixes unit conversion tests commit a5205c785b80862a17e9685bb07a50ecdf1caa19 Author: Florian Lentsch Date: Wed Jan 3 11:04:10 2024 +0100 Closes #26 commit 275306f9c29a4b62865b57c84f90f43670bb973f Author: Florian Lentsch Date: Wed Jan 3 09:10:57 2024 +0100 Changes for #10 and #25 commit 00b81f55c2645772833b660fdbd728f2a0e780a9 Author: Florian Lentsch Date: Tue Jan 2 18:16:38 2024 +0100 Closes #26 commit 0478db26f0701e647a228f404972c9b767e73fb9 Author: Florian Lentsch Date: Sat Dec 30 16:51:23 2023 +0100 Fixes incorrect article units migration commit 68de9538a8d4ef4fb61723479973e24c6ffcbea9 Author: Florian Lentsch Date: Sat Dec 30 11:05:28 2023 +0100 Fixes bug in stock takings commit 262e4753fe0829b38e6ff7563a903d78722b03dc Author: Florian Lentsch Date: Fri Dec 29 18:49:29 2023 +0100 Fixed exports commit 948c480fcb6708f79c210ed6aa424259bcda1b02 Author: Florian Lentsch Date: Fri Dec 29 17:10:32 2023 +0100 Some more bugfixing commit 380f279dcc1613cca60ca3845eddfd90a20ec363 Author: Florian Lentsch Date: Wed Dec 27 20:15:07 2023 +0100 Bugfixing commit 6b8c1124d7970b4f71ce80bf553c388135921809 Author: Florian Lentsch Date: Tue Dec 26 16:34:54 2023 +0100 Another fix for #28 commit 34c198ff6e21cfd43cb99d648748d6e1e01199ba Author: Florian Lentsch Date: Fri Dec 15 19:19:00 2023 +0100 Another fix for #7 commit 240f2d2ef3743163b7f094d28020c1d7fc559c71 Author: Florian Lentsch Date: Fri Dec 15 10:15:09 2023 +0100 Some formatting issues commit 1942755f30707e4978c94b4372724c3eba29b9d5 Author: Florian Lentsch Date: Sun Dec 10 19:39:26 2023 +0100 On #25: Persist and display migration state (button no longer available if migration has been performed once) commit 85cb33c68d2e1988d2908bf514412bb6540bbe38 Author: Florian Lentsch Date: Sun Dec 10 14:45:22 2023 +0100 Closes #28 commit 3cb2c0ee0a7ea0e50fce98578b5a02d8f1e4c708 Author: Florian Lentsch Date: Fri Dec 1 09:49:19 2023 +0100 Enhanced units display in dropdowns and order view commit be321b0de79a8390c7dcbe046a0d51871e5978c7 Author: Florian Lentsch Date: Fri Dec 1 09:11:18 2023 +0100 On #25: Moved auto conversion unit tests commit c56cf443c108e62393ca286747dce485f9e6f79f Author: Florian Lentsch Date: Sun Nov 19 16:47:44 2023 +0100 Started with #25 commit 56acdcb8e5ff7eac9239d4fb384f70a99fee0def Author: Florian Lentsch Date: Sat Nov 4 16:38:25 2023 +0100 First implementations of #24 and #10 commit 700f9a5a19e44999ec46e99302f25b1ec1147266 Author: Florian Lentsch Date: Sun Oct 29 18:00:19 2023 +0100 Added more available units commit 241e8360962276b2e12808b8ca6382a1575752b9 Author: Florian Lentsch Date: Sat Oct 28 14:15:04 2023 +0200 Fixed some of the errors in https://github.com/datasets/unece-units-of-measure by script commit 4aad2234a34c6f863254eb223f0cb9f71cede1af Author: Florian Lentsch Date: Sat Oct 28 11:18:30 2023 +0200 Swapped XPA for XPK commit 948ef325014ad1489760de9a31ea9963168122d4 Author: Florian Lentsch Date: Fri Oct 27 15:36:02 2023 +0200 Article form usability and bug fixes commit 0002749a1955d33a2c31be40be47f405d2a65fe9 Author: Florian Lentsch Date: Sat Oct 21 13:25:27 2023 +0200 Implemented edit all form commit 3353cc7ec635afde04fe24cf328d730f61466f9e Author: Florian Lentsch Date: Fri Oct 20 12:45:10 2023 +0200 shared articles list design enhancements commit b3218edad4f76373fb1ec7a605965a20e567a4f0 Author: Florian Lentsch Date: Sat Oct 14 10:30:27 2023 +0200 Fix for #19 commit 67c0e3812b632aa14e5d55eec5b9fd8bd0cbf28c Author: Florian Lentsch Date: Thu Oct 12 11:32:04 2023 +0200 Fixes conversion error with stock articles commit a4316a1cfe234d087bb02d635d4db75261b3a45f Author: Florian Lentsch Date: Fri Jun 23 15:24:36 2023 +0200 enable article sync via https commit b8574ee20de845235da1f75741f352e9d7142c84 Author: Florian Lentsch Date: Fri Apr 28 16:27:49 2023 +0200 Single article import form for #19 commit a1965141200ca2bd04e8ff674d2d0466c8487181 Author: Florian Lentsch Date: Sun Apr 23 19:15:25 2023 +0200 Continued with link generation for #19 commit c5a63c76c4a9afadcededb1083a38f9ea180b1a3 Author: Florian Lentsch Date: Sat Apr 22 18:01:10 2023 +0200 Added link generation for #19 commit 5e2c91b6c94cbd12ab55b1c9135192aff3336920 Author: Florian Lentsch Date: Sat Apr 8 18:31:57 2023 +0200 Continued with #19 commit 9337d08f52bebb0527d99b3b31dbe4b10ba74a6d Author: Florian Lentsch Date: Fri Apr 7 14:44:05 2023 +0200 More additions to #19 commit 72c953fd7bddcee715b22afae4e2868264702db7 Author: Florian Lentsch Date: Sat Mar 25 14:43:57 2023 +0100 Continued with #19 commit 748f8b002635a1648c3efa5311d84ad0d1c5bc33 Author: Florian Lentsch Date: Sat Mar 11 16:02:39 2023 +0100 Started with #19 commit 4c9a831bcade3932b3d651104c3761b09fdf8232 Author: Florian Lentsch Date: Fri Mar 10 20:14:53 2023 +0100 Added temporary fake article JSON import generator commit 64493b81f8cfe0bfd8046be278caa53d14661a9e Author: Florian Lentsch Date: Fri Mar 10 17:30:03 2023 +0100 Partial fixes for #14 (will all be replaced by #20) commit c3f5168b275d668299f059bad84c4474ecd1f40d Author: Florian Lentsch Date: Sat Feb 11 12:37:46 2023 +0100 Closes #18 commit ef0660b92ebe4122369c46a468905de9d58fedf9 Author: Florian Lentsch Date: Fri Feb 10 10:33:02 2023 +0100 Fixes #17 see https://forum.foodcoops.at/t/foodsoft-artikeleinheiten-hackerton-start-fixieren/5951/120 commit 4b20a396f71187899dc5cc130bb74f7828b507c3 Author: Florian Lentsch Date: Sun Dec 11 11:33:44 2022 +0100 Fixes issue with article versioning (wrong ID in update) commit 4e1d3a9c55b0e0011bc5eb889fc38b0289befaf0 Author: Florian Lentsch Date: Sun Nov 27 20:07:25 2022 +0100 Fixed Stock logic and started repairing specs commit 3fa352695c42d4de6eba90a278dd27e069fb6a24 Author: Florian Lentsch Date: Sun Nov 27 15:24:25 2022 +0100 TODOs commit f2e6b76e4262a8c29104987a76b688d4e5a44f9a Author: Florian Lentsch Date: Sat Nov 26 18:47:28 2022 +0100 Proposal for #13 commit 4a7d4aa584e16144770c23f9ea716796e408e93f Author: Florian Lentsch Date: Fri Nov 25 11:38:26 2022 +0100 Closes #7 commit cd2caf0a70a2eab0e6671a389049a98bbaa7ef54 Merge: 1907b021 aa307c3b Author: lentschi Date: Sat Nov 26 17:09:52 2022 +0100 Merge pull request #5 from foodcoopsat/feature/article_versioning_two_tables_2 Article versioning refactoring commit aa307c3b8d3c3fe5892608491fb87be1f1261dce Author: Florian Lentsch Date: Fri Nov 18 12:10:49 2022 +0100 Basic ordering workflow and exports are functioning commit 117bca3e35014e92b01d4a31898509eb308878c5 Author: Florian Lentsch Date: Fri Nov 18 10:37:23 2022 +0100 Article model before_save versioning logic commit edc199bbcc840efed2e1d5257d546c6ed3f27fc4 Author: Florian Lentsch Date: Sun Nov 6 18:35:57 2022 +0100 Adapted model relations & adapted hackathon seeds commit cfbe59d549476ad45aa28a47eccbc8cdc9b36fa8 Author: Florian Lentsch Date: Sun Nov 6 18:31:51 2022 +0100 Article versioning - Global replaces: Case-sensitive & regex: Case-sensitive & regex: \.price\.(?!to_) -> .article_version. oa\.article(?!_) -> oa.article_version order_article\.article(?!_) -> order_article.article_version \.article_version\.supplier(?!_) -> .article_version.article.supplier oa\.article_id -> oa.article_version.article_id (? article_version ArticlePrice -> ArticleVersion Exclude: ./db/migrate/**/*.rb Renames: app/models/article_price.rb -> app/models/article_version.rb app/views/shared/_article_price_info.html.haml -> app/views/shared/_article_version_info.html.haml commit 8d04a029d590142ac7504c229743ef6ab64f6a50 Author: Florian Lentsch Date: Wed Oct 26 14:23:51 2022 +0200 Article versioning schema migration commit 1907b0216654c8fe32649f7bcca2cde5bd92a0f3 Author: Florian Lentsch Date: Fri Oct 21 13:22:08 2022 +0200 Adapted article price to use supplier_order_unit as base commit 9a31c07697b52d22b32114f959810e18e0ec55c7 Author: Florian Lentsch Date: Fri Jul 22 15:48:14 2022 +0200 Reduced seeded price for potatoes commit c72ef551cdc5ceb1c8abc0e20875f17d27f1032e Author: Florian Lentsch Date: Fri Jul 22 15:43:40 2022 +0200 Fixes error in unit conversion commit 9e6d6e4cbb5750138cdc36c33681be5960603368 Author: Florian Lentsch Date: Fri Jul 22 12:04:58 2022 +0200 Fixes warnings in order accounting commit 354a0b6a5231cdcc78b1f5bed1d3ce6002b4ec7b Author: Florian Lentsch Date: Fri Jul 22 10:33:59 2022 +0200 Fixes missing units calculation with minimum order quantity commit fda2bb43446b39a350d1f690135e78efbf12a07b Author: Florian Lentsch Date: Mon Jul 18 07:51:48 2022 +0200 Some more price calculation fixes commit 03141330f70fe60f80a5e9186e0dec4088c2d50a Author: Florian Lentsch Date: Sun Jul 17 11:58:04 2022 +0200 Revert "Fixes price calculation" This reverts commit d79ba4da31f7538402cb1e9356aff33b3f5219cc. commit d79ba4da31f7538402cb1e9356aff33b3f5219cc Author: Florian Lentsch Date: Sun Jul 17 11:49:03 2022 +0200 Fixes price calculation commit 9a7f59d847ce4bc95636d782661934eadc7c8db6 Author: Florian Lentsch Date: Sun Jul 17 11:42:38 2022 +0200 Fixes NaN error in receive form commit def95dee7aad33fbeab5f5a5c802ff4ee472d4f8 Author: Florian Lentsch Date: Sun Jul 17 11:38:01 2022 +0200 Unit conversion fixes for receive form commit 045e0d1090dddf29b9beddc7c9da12a10995fc10 Author: Florian Lentsch Date: Sat Jul 16 15:02:14 2022 +0200 Fixed group_order_article_quantities floating point values commit 2405832bc4c245f67c14cd5e4e7765e49ef28794 Author: Florian Lentsch Date: Sat Jul 16 10:01:57 2022 +0200 Fixes floating point error when posting group order commit 1e328cd24746888c9225ed0414845cdc177c3ba8 Author: Florian Lentsch Date: Fri Jul 15 15:41:58 2022 +0200 Fixed calculation errors commit 32f478d42031ea20c3ff2fc6fe219f0370e8c260 Author: Florian Lentsch Date: Fri Jul 15 13:33:29 2022 +0200 Removed TODO commit e9b89163cbe933ea2befa1c15f0f977deaac4271 Author: Florian Lentsch Date: Fri Jul 15 13:28:21 2022 +0200 Fixed rubocop warning commit be42e733851e7f9a6712208fa62f9e056763cfe9 Author: Florian Lentsch Date: Fri Jul 15 13:23:15 2022 +0200 Made order_articles units float commit 576001597689d5a1ab42926cd06f49df8093eb37 Author: Florian Lentsch Date: Tue Jul 12 20:43:54 2022 +0200 Added hackathon seeds and fixed some rounding issues commit b33728b6665419672a33f6b7eb2c39b86e97f212 Author: Florian Lentsch Date: Mon Jul 11 19:33:23 2022 +0200 Fixes pack size calculation commit ec31210e74721ad9acf2ec69fe7d1b038e9eeab8 Author: Florian Lentsch Date: Sun Jul 10 13:35:08 2022 +0200 Made goa quantity and tolerance accept floats commit 9a58885ae9f849f58a370b0402c1fc5c54fc6d14 Author: Florian Lentsch Date: Sun Jul 10 13:34:43 2022 +0200 Fixes bug in stepping commit b6b6b31764c25e2732451838d31f427d757565d8 Author: Florian Lentsch Date: Fri Jul 8 11:13:39 2022 +0200 group order form: round values commit 0f0bc0e76b2cdbdbaa57e7541963ece3db1276a1 Author: Florian Lentsch Date: Fri Jul 8 10:53:45 2022 +0200 group order granularity fixes commit c7760814f5dd4a88234639788fd3d7cf43ac1126 Author: Florian Lentsch Date: Fri Jul 8 09:36:40 2022 +0200 Don't show unit conversion field, if there's nothing to convert commit 8f330a7795094506847d4bedfdd076da33db18de Author: Florian Lentsch Date: Sun Jul 3 10:09:23 2022 +0200 Fixes conversion error, when there's no ratios commit 1bb36a4c3ca0e93673b25af0d446691055fea88f Author: Florian Lentsch Date: Fri Jul 1 14:42:34 2022 +0200 Fixes article form issues commit a2f085b1f8db1cd1a7211641c9012840ce37a095 Author: Florian Lentsch Date: Fri Jun 24 20:25:45 2022 +0200 Fixes error in accounting commit 49e3db0b368088d8de7e92cfb76a997c48823b78 Author: Florian Lentsch Date: Fri Jun 24 17:04:33 2022 +0200 Fixes another error in conversion field commit 63f67106eb6c5f88a987ea50d2f088d8fce22d20 Author: Florian Lentsch Date: Fri Jun 24 09:40:12 2022 +0200 Fixes error in conversion field commit 66b245907835664915352928cbde2d6cee0f0fa5 Author: Florian Lentsch Date: Sun Jun 19 12:19:52 2022 +0200 Fixes price in article form commit 870f5b7692ff32d431e4ed930ff8ee417c17698c Author: Florian Lentsch Date: Thu Jun 16 20:41:55 2022 +0200 Added unit convertes for billing article fields commit 35f3c5479c34426521399f7e5a3827113882a0d6 Author: Florian Lentsch Date: Thu Jun 16 20:05:26 2022 +0200 Fixed conversion error in article form commit 0acb04880f443ae5c67bc8b7ce4e564d2a4b6dfa Author: Florian Lentsch Date: Thu Jun 16 12:25:01 2022 +0200 Done with billing in a first version commit 12228da301c1a01c3b50c2698b0550abe5021592 Author: Florian Lentsch Date: Sun Jun 12 11:34:40 2022 +0200 Started with billing commit 1b484f3b64b98088c8d472558a69aa6e335ddf0e Author: Florian Lentsch Date: Mon Jun 6 22:09:16 2022 +0200 Receive form edit article commit c2579a168b5a1db1af7df19cfd2d656f30f5b030 Author: Florian Lentsch Date: Mon Jun 6 10:19:05 2022 +0200 Receive page adaptations commit 7b8f1b0683182d78b1ba1f7b35994f4caf4a1948 Author: Florian Lentsch Date: Sun Jun 5 20:26:34 2022 +0200 Some more minimum order quantity fixes commit cde1f1d76af2b694e438d0fb372bea108d0c9420 Author: Florian Lentsch Date: Sun Jun 5 15:22:22 2022 +0200 Minimum order quantity fixes commit ec5354607920391dc9da4c54aa1ce059951dac28 Author: Florian Lentsch Date: Sun May 15 13:02:53 2022 +0200 Started with minimum order quantity commit 65f3bddec7df33c8d998526aca2c16791ee75b19 Author: Florian Lentsch Date: Sat May 14 12:36:33 2022 +0200 Article form fix commit 1708d23cf4602af358fe43b0b4b2a0a8ab294989 Author: Florian Lentsch Date: Fri May 13 13:38:17 2022 +0200 Conversion fixes commit b7f32d7719b08e08593ddaca7dcacbed3dfb9a36 Author: Florian Lentsch Date: Fri Apr 29 14:53:22 2022 +0200 Added unit to article_prices commit 4ecf7d4267619389a88eb87ab598ff212618b51f Author: Florian Lentsch Date: Sat Apr 23 20:23:02 2022 +0200 Adapted order fax (though this is still a matter of debate) commit 98f6bbf65befa09994e82c5014d4a2d430f39429 Author: Florian Lentsch Date: Fri Apr 22 14:35:23 2022 +0200 Duplicate new properties in ArticlePrice commit 07ed7a063532065f9d898be4c461f1f4e7f415e4 Author: Florian Lentsch Date: Fri Apr 15 15:35:31 2022 +0200 Continued with group order form commit d60b547b66783e8c6af0e5ccb5d820bdbc0e65ba Author: Florian Lentsch Date: Sat Apr 2 19:05:33 2022 +0200 Started remodelling group order form commit a1f4060895829231101a732f8c650f888b4aede7 Author: Florian Lentsch Date: Sun Mar 27 14:51:52 2022 +0200 Unit conversions work in a first version commit f9ba668f0e5d4fef36a62ce496c48ab797ed81db Author: Florian Lentsch Date: Fri Mar 25 11:38:12 2022 +0100 Flat ratios commit d86be97b221bdc54d1b30a4645ddc0fcac694758 Author: Florian Lentsch Date: Fri Mar 18 12:10:17 2022 +0100 Added units file convertor and result to enable easier transformations between units with the same base unit commit 40fb822b1d00e84cd1234fcb50cf6f352d360967 Author: Florian Lentsch Date: Fri Mar 4 09:54:38 2022 +0100 Articles form - all fields implemented in a first version commit f020ebb2a0a30e14930952bf24651ef5f5e122d9 Author: Florian Lentsch Date: Wed Mar 2 18:36:09 2022 +0100 Article edit form: Moved js to separate file commit 5e8e093cc4df643d22e0a880fc1d2a690eefdb7f Author: Florian Lentsch Date: Fri Feb 25 10:44:38 2022 +0100 Renamed fields commit 65e26a3dfc89bf830686d7bbaf4f5381394f4eab Author: Florian Lentsch Date: Fri Feb 25 09:18:51 2022 +0100 Fixes docker-compose foodsoft build commit 038b00ea9f18d6dbc754dd19ff3c0d2d98c03d26 Author: Florian Lentsch Date: Sun Feb 20 19:36:18 2022 +0100 Saving conversions works in a first version commit 01537858259e2738d2efa385c99c53be7d69d2bf Author: Florian Lentsch Date: Sun Feb 20 13:50:59 2022 +0100 Added minimum_order_amount to articles commit 57cb7c1393faa9c5837d7562888402de873b5271 Merge: 979eebc6 f371f70e Author: Ostler, Leonard Date: Sun Feb 20 13:44:59 2022 +0100 Merge branch 'master' of https://github.com/foodcoopsat/foodsoft_hackathon commit 979eebc62a41af2393d716488e6c6b9e5278abde Author: Ostler, Leonard Date: Sun Feb 20 13:43:47 2022 +0100 article data UI for new unit handling commit f371f70e1bb78f6c7681b00ce9cf437c22ea48ba Author: Florian Lentsch Date: Sun Feb 20 13:26:23 2022 +0100 Added article unit conversions model and fixed migration commit 41adb6508ac23ff7bc752d57ae6313dde85ec1e3 Author: Florian Lentsch Date: Sun Feb 20 12:16:08 2022 +0100 Added units of measure from https://datahub.io/core/unece-units-of-measure commit e0a7073f744047c3f82ba70687fcca2745b3425f Author: Florian Lentsch Date: Sun Feb 20 12:01:22 2022 +0100 rubocop fixes commit 5194e96006f44c95706ae127a3d9fb1bd0c19689 Author: Florian Lentsch Date: Sat Feb 19 23:07:18 2022 +0100 First attempt at unit migration --- .eslintrc.js | 14 + .rubocop.yml | 2 + .rubocop_todo.yml | 1743 +- Dockerfile-dev | 2 +- Gemfile | 1 + Gemfile.lock | 2 + HACKATHON_TODOS.md | 12 + Procfile | 3 +- Rakefile | 2 +- app/assets/javascripts/application.js | 28 +- app/assets/javascripts/article-form.js | 592 + app/assets/javascripts/big.js | 1043 + app/assets/javascripts/delta_input.js | 14 +- app/assets/javascripts/group-order-form.js | 232 + app/assets/javascripts/migrate-units-form.js | 142 + app/assets/javascripts/receive-order-form.js | 153 + .../javascripts/unit-conversion-field.js | 251 + app/assets/javascripts/units-converter.js | 40 + app/assets/stylesheets/article.less | 38 + .../bootstrap_and_overrides.css.less | 129 + app/assets/stylesheets/delta_input.less | 4 +- app/assets/stylesheets/group_order.less | 16 + app/assets/stylesheets/main.sass | 134 +- .../admin/bank_accounts_controller.rb | 26 +- .../admin/bank_gateways_controller.rb | 10 +- app/controllers/admin/configs_controller.rb | 26 +- app/controllers/admin/finances_controller.rb | 6 +- ...inancial_transaction_classes_controller.rb | 20 +- .../financial_transaction_types_controller.rb | 20 +- .../admin/mail_delivery_status_controller.rb | 12 +- .../admin/ordergroups_controller.rb | 13 +- app/controllers/admin/users_controller.rb | 18 +- .../admin/workgroups_controller.rb | 8 +- app/controllers/api/v1/articles_controller.rb | 58 + app/controllers/api/v1/base_controller.rb | 35 +- .../api/v1/order_articles_controller.rb | 2 +- .../user/financial_transactions_controller.rb | 3 +- .../user/group_order_articles_controller.rb | 7 +- .../api/v1/user/ordergroup_controller.rb | 4 +- app/controllers/application_controller.rb | 18 +- .../article_categories_controller.rb | 8 +- app/controllers/article_units_controller.rb | 63 + app/controllers/articles_controller.rb | 401 +- app/controllers/concerns/auth.rb | 38 +- app/controllers/concerns/auth_api.rb | 14 +- app/controllers/concerns/foodcoop_scope.rb | 4 +- app/controllers/concerns/locale.rb | 12 +- app/controllers/concerns/send_order_pdf.rb | 2 +- app/controllers/deliveries_controller.rb | 22 +- app/controllers/feedback_controller.rb | 7 +- .../finance/balancing_controller.rb | 55 +- .../finance/bank_accounts_controller.rb | 8 +- .../finance/bank_transactions_controller.rb | 30 +- .../finance/financial_links_controller.rb | 14 +- .../financial_transactions_controller.rb | 78 +- .../finance/invoices_controller.rb | 30 +- .../finance/ordergroups_controller.rb | 4 +- .../foodcoop/ordergroups_controller.rb | 10 +- app/controllers/foodcoop/users_controller.rb | 7 +- .../foodcoop/workgroups_controller.rb | 8 +- .../group_order_articles_controller.rb | 6 +- app/controllers/group_orders_controller.rb | 70 +- app/controllers/home_controller.rb | 54 +- app/controllers/invites_controller.rb | 8 +- app/controllers/login_controller.rb | 23 +- app/controllers/order_articles_controller.rb | 53 +- app/controllers/order_comments_controller.rb | 6 +- app/controllers/orders_controller.rb | 112 +- app/controllers/sessions_controller.rb | 6 +- app/controllers/stock_takings_controller.rb | 12 +- app/controllers/stockit_controller.rb | 113 +- app/controllers/styles_controller.rb | 2 +- app/controllers/supplier_shares_controller.rb | 15 + app/controllers/suppliers_controller.rb | 47 +- app/controllers/tasks_controller.rb | 50 +- app/controllers/users_controller.rb | 2 +- app/documents/order_by_articles.rb | 16 +- app/documents/order_by_groups.rb | 14 +- app/documents/order_fax.rb | 78 +- app/documents/order_matrix.rb | 28 +- app/helpers/admin/configs_helper.rb | 19 +- app/helpers/admin/ordergroups_helper.rb | 4 +- app/helpers/application_helper.rb | 92 +- app/helpers/articles_helper.rb | 76 +- app/helpers/deliveries_helper.rb | 22 +- app/helpers/finance/balancing_helper.rb | 6 +- app/helpers/finance/invoices_helper.rb | 4 +- app/helpers/group_order_articles_helper.rb | 25 +- app/helpers/group_orders_helper.rb | 34 +- app/helpers/orders_helper.rb | 110 +- app/helpers/stockit_helper.rb | 10 +- app/helpers/suppliers_helper.rb | 10 +- app/helpers/tasks_helper.rb | 12 +- app/inputs/delta_input.rb | 9 +- app/mailers/mailer.rb | 18 +- app/models/article.rb | 277 +- app/models/article_BACKUP_56136.rb | 388 + app/models/article_BASE_56136.rb | 256 + app/models/article_LOCAL_56136.rb | 271 + app/models/article_REMOTE_56136.rb | 279 + app/models/article_category.rb | 21 +- app/models/article_price.rb | 30 - app/models/article_unit.rb | 43 + app/models/article_unit_ratio.rb | 6 + app/models/article_version.rb | 177 + app/models/bank_account.rb | 14 +- app/models/bank_gateway.rb | 2 +- app/models/bank_transaction.rb | 12 +- app/models/concerns/custom_fields.rb | 2 +- app/models/concerns/find_each_with_order.rb | 4 +- .../concerns/mark_as_deleted_with_name.rb | 2 +- app/models/concerns/price_calculation.rb | 44 +- app/models/delivery.rb | 12 +- app/models/financial_link.rb | 4 +- app/models/financial_transaction.rb | 20 +- app/models/financial_transaction_class.rb | 2 +- app/models/financial_transaction_type.rb | 10 +- app/models/group.rb | 6 +- app/models/group_order.rb | 63 +- app/models/group_order_article.rb | 92 +- app/models/group_order_article_quantity.rb | 2 +- app/models/invite.rb | 20 +- app/models/invoice.rb | 38 +- app/models/membership.rb | 2 +- app/models/order.rb | 197 +- app/models/order_article.rb | 177 +- app/models/order_comment.rb | 4 +- app/models/ordergroup.rb | 52 +- app/models/periodic_task_group.rb | 14 +- app/models/shared_article.rb | 26 - app/models/shared_supplier.rb | 33 - app/models/stock_article.rb | 12 +- app/models/stock_change.rb | 6 +- app/models/stock_event.rb | 2 +- app/models/supplier.rb | 177 +- app/models/task.rb | 48 +- app/models/user.rb | 102 +- app/models/workgroup.rb | 18 +- app/serializers/article_unit_serializer.rb | 3 + app/serializers/order_article_serializer.rb | 2 +- .../article_units/_create_link.html.haml | 1 + .../article_units/_destroy_link.html.haml | 1 + app/views/article_units/_rows.html.haml | 16 + app/views/article_units/create.js.haml | 2 + app/views/article_units/destroy.js.haml | 2 + app/views/article_units/index.html.haml | 60 + app/views/article_units/search.js.haml | 1 + app/views/articles/_article.html.haml | 4 +- app/views/articles/_edit_all_table.html.haml | 69 +- app/views/articles/_form.html.haml | 21 +- .../articles/_import_article_added.js.erb | 4 - .../articles/_import_search_results.haml | 33 - app/views/articles/_sync.html.haml | 8 +- app/views/articles/_sync_table.html.haml | 79 +- app/views/articles/create.js.haml | 3 + app/views/articles/destroy.js.haml | 3 + app/views/articles/edit.html.haml | 2 + app/views/articles/edit_all.html.haml | 3 +- app/views/articles/index.html.haml | 22 +- app/views/articles/migrate_units.html.haml | 74 + app/views/articles/new.js.haml | 2 +- .../articles/prepare_units_migration.haml | 8 + app/views/articles/shared.js.haml | 1 - app/views/articles/update.js.haml | 2 +- .../_stock_article_for_adding.html.haml | 4 +- .../deliveries/_stock_change_fields.html.haml | 4 +- app/views/deliveries/add_stock_change.js.erb | 12 +- .../balancing/_group_order_articles.html.haml | 5 +- .../balancing/_order_article.html.haml | 22 +- app/views/finance/balancing/new.html.haml | 11 +- app/views/finance/balancing/new.js.haml | 5 + .../new_on_order_article_create.js.erb | 14 +- .../new_on_order_article_update.js.erb | 10 +- .../group_order_articles/_form.html.haml | 4 +- app/views/group_orders/_form.html.haml | 75 +- app/views/group_orders/show.html.haml | 14 +- app/views/mailer/order_received.text.haml | 4 +- app/views/mailer/order_result.text.haml | 2 +- app/views/order_articles/_edit.html.haml | 21 +- app/views/order_articles/edit.js.haml | 4 +- app/views/order_articles/new.js.haml | 2 +- app/views/orders/_articles.html.haml | 18 +- app/views/orders/_edit_amount.html.haml | 22 +- app/views/orders/_edit_amounts.html.haml | 94 +- app/views/orders/_form.html.haml | 4 +- app/views/orders/add_article.js.erb | 6 +- .../receive_on_order_article_create.js.erb | 10 +- .../receive_on_order_article_update.js.erb | 34 +- app/views/orders/show.html.haml | 3 + app/views/orders/show.js.erb | 7 + .../shared/_article_fields_price.html.haml | 22 +- .../shared/_article_fields_units.html.haml | 37 +- app/views/shared/_article_unit_ratio.haml | 19 + ...l.haml => _article_version_info.html.haml} | 0 .../articles_by/_article_single.html.haml | 8 +- .../articles_by/_article_single_goa.html.haml | 3 +- .../shared/articles_by/_articles.html.haml | 2 +- .../articles_by/_group_single_goa.html.haml | 10 +- .../shared/articles_by/_groups.html.haml | 1 - .../_unit_conversion_popover_template.haml | 12 + .../stock_takings/_stock_change.html.haml | 2 +- app/views/stockit/_form.html.haml | 31 +- app/views/stockit/_stock_article.html.haml | 2 +- .../supplier_shares/_share_link.html.haml | 16 + app/views/supplier_shares/update.js.haml | 2 + app/views/suppliers/_form.html.haml | 8 +- .../suppliers/_import_search_results.haml | 29 + app/views/suppliers/index.html.haml | 2 - app/views/suppliers/remote_articles.js.haml | 22 + .../suppliers/shared_suppliers.html.haml | 25 - app/views/suppliers/show.html.haml | 7 +- bin/autospec | 4 +- bin/mailcatcher | 4 +- bin/resque | 4 +- bin/resque-web | 4 +- bin/rspec | 4 +- bin/yarn | 12 +- config.ru | 2 +- config/app_config.yml.SAMPLE | 9 + config/application.rb | 16 +- config/database_shared.yml.MYSQL_SAMPLE | 9 + config/environments/production.rb | 21 +- config/i18n-js.yml | 4 +- config/initializers/currency_display.rb | 7 +- config/initializers/doorkeeper.rb | 8 +- config/initializers/exception_notification.rb | 8 +- config/initializers/extensions.rb | 6 +- config/initializers/rack.rb | 2 +- config/initializers/rails6_backports.rb | 6 +- config/initializers/ruby_units.rb | 14 +- config/initializers/secret_token.rb | 10 +- config/initializers/session_store.rb | 2 +- config/initializers/simple_form_bootstrap.rb | 4 +- config/locales/de.yml | 71 +- config/locales/en.yml | 78 +- config/locales/es.yml | 14 +- config/locales/fr.yml | 12 +- config/locales/nl.yml | 2 +- config/navigation.rb | 32 +- config/puma.rb | 13 +- config/routes.rb | 70 +- config/schedule.rb | 16 +- config/spring.rb | 4 +- config/units-of-measure/locales/unece_de.yml | 763 + config/units-of-measure/locales/unece_en.yml | 761 + .../units-of-measure/translations_piece.csv | 69 + .../translations_scalar-de.csv | 120 + .../translations_scalar-en.csv | 121 + .../units-of-measure/un-ece-20-remastered.yml | 22825 ++++++++++++++++ config/units-of-measure/un-ece-20.yml | 15383 +++++++++++ config/units-of-measure/un-ece-21.yml | 1202 + db/migrate/001_create_users.rb | 30 +- db/migrate/002_create_groups.rb | 33 +- db/migrate/003_create_suppliers.rb | 20 +- db/migrate/004_create_article_meta.rb | 12 +- .../005_create_financial_transactions.rb | 18 +- db/migrate/006_create_articles.rb | 28 +- db/migrate/007_create_article_prices.rb | 22 +- db/migrate/008_create_orders.rb | 92 +- db/migrate/009_create_order_results.rb | 34 +- db/migrate/011_create_comments.rb | 18 +- db/migrate/012_create_order_clearing.rb | 6 +- db/migrate/013_add_messaging.rb | 14 +- db/migrate/014_create_tasks.rb | 20 +- db/migrate/015_change_result_quantities.rb | 8 +- db/migrate/018_create_invites.rb | 10 +- .../019_remove_uniqueness_of_article_name.rb | 6 +- db/migrate/021_remove_table_article_prices.rb | 86 +- db/migrate/022_add_required_user_for_task.rb | 4 +- db/migrate/024_add_deposit_defaults.rb | 3 +- db/migrate/025_extend_comments.rb | 4 +- .../20090120184410_road_to_version_three.rb | 62 +- .../20090317175355_add_profit_to_orders.rb | 2 +- ...75756_create_pages.foodsoft_wiki_engine.rb | 2 +- ...31156_modify_group_order_article_result.rb | 2 +- .../20090907120012_add_missing_indexes.rb | 39 +- .../20110507184920_add_duration_to_tasks.rb | 2 +- ...7192928_add_task_duration_to_workgroups.rb | 2 +- ..._next_weekly_tasks_number_to_workgroups.rb | 2 +- .../20130622095040_move_weekly_tasks.rb | 30 +- ...0130702113610_update_group_order_totals.rb | 17 +- db/migrate/20130718183100_create_settings.rb | 2 +- .../20130718183101_migrate_user_settings.rb | 7 +- .../20130920201529_allow_missing_nick.rb | 4 +- ...result_computed_to_group_order_articles.rb | 2 +- ...73000_delete_empty_group_order_articles.rb | 3 +- ...20140921104907_remove_stale_memberships.rb | 2 +- ...160217194036_add_role_invoices_to_group.rb | 2 +- ...0160218151041_add_attachment_to_invoice.rb | 2 +- ...e_financial_transaction_class_and_types.rb | 8 +- .../20160224201529_allow_stock_group_order.rb | 4 +- ...ail_to_message.foodsoft_messages_engine.rb | 2 +- ...70801000000_create_mail_delivery_status.rb | 4 +- .../20171002000000_create_financial_links.rb | 6 +- ...0000_create_polls.foodsoft_polls_engine.rb | 4 +- ...ease_choices_size.foodsoft_polls_engine.rb | 2 +- ...te_printer_jobs.foodsoft_printer_engine.rb | 2 +- ...te_message_recipients.foodsoft_messages.rb | 2 +- ...te_active_storage_tables.active_storage.rb | 2 +- ...0257_introduce_received_state_in_orders.rb | 2 +- ...026102300_alter_articles_add_versioning.rb | 217 + ...2301_alter_articles_add_more_unit_logic.rb | 106 + ...14122017_alter_suppliers_sharing_fields.rb | 19 + .../20231104141204_create_article_units.rb | 29 + ...d_unit_migration_completed_to_suppliers.rb | 5 + db/schema.rb | 1088 +- db/seeds/hackathon.seeds.rb | 158 + db/seeds/hackathon_de.seeds.rb | 158 + db/seeds/minimal.seeds.rb | 36 +- db/seeds/seed_helper.rb | 6 +- db/seeds/small.en.seeds.rb | 457 +- db/seeds/small.nl.seeds.rb | 457 +- ...204112447_create_articles_and_suppliers.rb | 50 + db_shared/schema.rb | 60 + db_shared/seeds.rb | 0 doc/SETUP_DEVELOPMENT_DOCKER.md | 2 + docker-compose-dev.yml | 2 + lib/apple_bar.rb | 14 +- lib/article_units_lib.rb | 181 + lib/articles_csv.rb | 48 +- lib/bank_account_connector.rb | 30 +- lib/bank_account_information_importer.rb | 6 +- lib/bank_transaction_reference.rb | 2 +- lib/date_time_attribute_validate.rb | 58 +- lib/foodsoft/expansion_variables.rb | 6 +- lib/foodsoft_config.rb | 27 +- lib/foodsoft_date_util.rb | 10 +- lib/foodsoft_file.rb | 53 +- lib/foodsoft_mail_receiver.rb | 28 +- lib/invoices_csv.rb | 2 +- lib/order_csv.rb | 19 +- lib/order_pdf.rb | 44 +- lib/order_txt.rb | 19 +- lib/ordergroups_csv.rb | 4 +- lib/render_csv.rb | 4 +- lib/render_pdf.rb | 6 +- lib/tasks/db_shared.rake | 54 + lib/tasks/foodsoft.rake | 64 +- lib/tasks/foodsoft_setup.rake | 85 +- lib/tasks/multicoops.rake | 24 +- lib/tasks/resque.rake | 26 +- lib/tasks/rspec.rake | 2 +- lib/tasks/temp_export_supplier.rake | 33 + lib/tasks/units.rake | 136 + lib/token_verifier.rb | 10 +- .../current_orders/articles_controller.rb | 10 +- .../current_orders/group_orders_controller.rb | 14 +- .../current_orders/ordergroups_controller.rb | 10 +- .../documents/multiple_orders_by_articles.rb | 18 +- .../documents/multiple_orders_by_groups.rb | 24 +- .../app/helpers/current_orders_helper.rb | 3 +- .../articles/_article_info.html.haml | 18 +- .../articles/_articles.html.haml | 2 +- .../articles/_ordergroups.html.haml | 2 +- .../current_orders/articles/show.html.haml | 2 +- .../group_orders/_result.html.haml | 16 +- .../ordergroups/_article.html.haml | 6 +- .../ordergroups/_articles.html.haml | 2 +- plugins/current_orders/config/routes.rb | 8 +- .../foodsoft_current_orders.gemspec | 23 +- .../lib/foodsoft_current_orders.rb | 4 +- .../lib/foodsoft_current_orders/engine.rb | 11 +- .../lib/foodsoft_current_orders/version.rb | 2 +- plugins/discourse/Rakefile | 4 +- .../app/controllers/discourse_controller.rb | 4 +- .../controllers/discourse_login_controller.rb | 6 +- .../controllers/discourse_sso_controller.rb | 4 +- plugins/discourse/foodsoft_discourse.gemspec | 24 +- .../foodsoft_discourse/redirect_to_login.rb | 4 +- .../lib/foodsoft_discourse/version.rb | 2 +- plugins/documents/Rakefile | 4 +- .../app/controllers/documents_controller.rb | 30 +- plugins/documents/foodsoft_documents.gemspec | 26 +- .../lib/foodsoft_documents/engine.rb | 6 +- .../lib/foodsoft_documents/version.rb | 2 +- plugins/links/Rakefile | 4 +- .../app/controllers/admin/links_controller.rb | 4 +- .../links/app/controllers/links_controller.rb | 10 +- plugins/links/foodsoft_links.gemspec | 24 +- plugins/links/lib/foodsoft_links/engine.rb | 20 +- plugins/links/lib/foodsoft_links/version.rb | 2 +- plugins/messages/Rakefile | 4 +- .../admin/messagegroups_controller.rb | 6 +- .../controllers/messagegroups_controller.rb | 6 +- .../app/controllers/messages_controller.rb | 39 +- .../messages/app/helpers/messages_helper.rb | 6 +- .../mail_receivers/messages_mail_receiver.rb | 46 +- plugins/messages/app/models/message.rb | 38 +- .../messages/app/models/message_recipient.rb | 2 +- plugins/messages/app/models/messagegroup.rb | 2 - plugins/messages/config/routes.rb | 4 +- .../20160226000000_add_email_to_message.rb | 2 +- plugins/messages/foodsoft_messages.gemspec | 32 +- plugins/messages/lib/foodsoft_messages.rb | 8 +- .../messages/lib/foodsoft_messages/engine.rb | 19 +- .../lib/foodsoft_messages/user_link.rb | 4 +- .../messages/lib/foodsoft_messages/version.rb | 2 +- plugins/polls/Rakefile | 4 +- .../polls/app/controllers/polls_controller.rb | 40 +- .../db/migrate/20181110000000_create_polls.rb | 4 +- .../20181120000000_increase_choices_size.rb | 2 +- plugins/polls/foodsoft_polls.gemspec | 24 +- plugins/polls/lib/foodsoft_polls/engine.rb | 6 +- plugins/polls/lib/foodsoft_polls/version.rb | 2 +- plugins/printer/Rakefile | 4 +- .../app/controllers/printer_controller.rb | 7 +- .../controllers/printer_jobs_controller.rb | 6 +- plugins/printer/config/routes.rb | 2 +- .../20181201000000_create_printer_jobs.rb | 2 +- plugins/printer/foodsoft_printer.gemspec | 26 +- .../printer/lib/foodsoft_printer/engine.rb | 13 +- .../foodsoft_printer/order_printer_jobs.rb | 10 +- .../printer/lib/foodsoft_printer/version.rb | 2 +- plugins/uservoice/foodsoft_uservoice.gemspec | 23 +- plugins/uservoice/lib/foodsoft_uservoice.rb | 10 +- .../lib/foodsoft_uservoice/version.rb | 2 +- plugins/wiki/Rakefile | 4 +- .../wiki/app/controllers/pages_controller.rb | 82 +- plugins/wiki/app/helpers/pages_helper.rb | 60 +- plugins/wiki/app/models/page.rb | 59 +- plugins/wiki/app/views/pages/all.rss.builder | 10 +- plugins/wiki/config/routes.rb | 10 +- .../db/migrate/20090325175756_create_pages.rb | 2 +- plugins/wiki/foodsoft_wiki.gemspec | 26 +- plugins/wiki/lib/foodsoft_wiki/engine.rb | 10 +- plugins/wiki/lib/foodsoft_wiki/mailer.rb | 10 +- plugins/wiki/lib/foodsoft_wiki/version.rb | 2 +- plugins/wiki/lib/foodsoft_wiki/wiki_parser.rb | 8 +- script/rails | 4 +- spec/api/v1/order_articles_spec.rb | 22 +- spec/api/v1/swagger_spec.rb | 138 +- .../v1/user/financial_transactions_spec.rb | 62 +- spec/api/v1/user/group_order_articles_spec.rb | 145 +- spec/api/v1/user/ordergroup_spec.rb | 24 +- spec/app_config.yml | 10 + spec/factories/article.rb | 44 +- spec/factories/article_category.rb | 7 + spec/factories/article_unit_ratio.rb | 10 + spec/factories/article_version.rb | 35 + spec/factories/doorkeeper.rb | 4 +- spec/factories/group_order.rb | 2 +- spec/factories/invoice.rb | 4 +- spec/factories/order.rb | 6 +- spec/factories/supplier.rb | 7 +- spec/factories/user.rb | 20 +- spec/integration/articles_spec.rb | 58 +- spec/integration/balancing_spec.rb | 86 +- spec/integration/config_spec.rb | 10 +- spec/integration/login_spec.rb | 23 +- spec/integration/order_spec.rb | 18 +- .../product_distribution_example_spec.rb | 24 +- spec/integration/receive_spec.rb | 36 +- spec/integration/session_spec.rb | 19 +- spec/integration/supplier_spec.rb | 17 +- spec/lib/article_units_lib_spec.rb | 136 + .../bank_account_information_importer_spec.rb | 31 +- spec/lib/bank_transaction_reference_spec.rb | 48 +- spec/lib/foodsoft_config_spec.rb | 2 +- spec/lib/foodsoft_mail_receiver_spec.rb | 82 +- spec/lib/token_verifier_spec.rb | 14 +- spec/models/article_spec.rb | 90 +- spec/models/bank_transaction_spec.rb | 70 +- spec/models/group_order_article_spec.rb | 44 +- spec/models/group_order_spec.rb | 6 +- spec/models/order_article_spec.rb | 82 +- spec/models/order_spec.rb | 84 +- spec/models/ordergroup_spec.rb | 12 +- spec/models/supplier_spec.rb | 72 +- spec/models/user_spec.rb | 46 +- spec/spec_helper.rb | 16 +- spec/support/api_helper.rb | 6 +- spec/support/api_oauth.rb | 2 +- spec/support/coverage.rb | 26 +- spec/support/faker.rb | 14 +- spec/support/integration.rb | 2 +- spec/support/session_helper.rb | 9 +- spec/support/shared_database.rb | 20 - 477 files changed, 54006 insertions(+), 5969 deletions(-) create mode 100644 .eslintrc.js create mode 100644 HACKATHON_TODOS.md mode change 100644 => 100755 Rakefile create mode 100644 app/assets/javascripts/article-form.js create mode 100644 app/assets/javascripts/big.js create mode 100644 app/assets/javascripts/group-order-form.js create mode 100644 app/assets/javascripts/migrate-units-form.js create mode 100644 app/assets/javascripts/receive-order-form.js create mode 100644 app/assets/javascripts/unit-conversion-field.js create mode 100644 app/assets/javascripts/units-converter.js create mode 100644 app/assets/stylesheets/article.less create mode 100644 app/assets/stylesheets/group_order.less create mode 100644 app/controllers/api/v1/articles_controller.rb create mode 100644 app/controllers/article_units_controller.rb create mode 100644 app/controllers/supplier_shares_controller.rb create mode 100644 app/models/article_BACKUP_56136.rb create mode 100644 app/models/article_BASE_56136.rb create mode 100644 app/models/article_LOCAL_56136.rb create mode 100644 app/models/article_REMOTE_56136.rb delete mode 100644 app/models/article_price.rb create mode 100644 app/models/article_unit.rb create mode 100644 app/models/article_unit_ratio.rb create mode 100644 app/models/article_version.rb delete mode 100644 app/models/shared_article.rb delete mode 100644 app/models/shared_supplier.rb create mode 100644 app/serializers/article_unit_serializer.rb create mode 100644 app/views/article_units/_create_link.html.haml create mode 100644 app/views/article_units/_destroy_link.html.haml create mode 100644 app/views/article_units/_rows.html.haml create mode 100644 app/views/article_units/create.js.haml create mode 100644 app/views/article_units/destroy.js.haml create mode 100644 app/views/article_units/index.html.haml create mode 100644 app/views/article_units/search.js.haml delete mode 100644 app/views/articles/_import_article_added.js.erb delete mode 100644 app/views/articles/_import_search_results.haml create mode 100644 app/views/articles/edit.html.haml create mode 100644 app/views/articles/migrate_units.html.haml create mode 100644 app/views/articles/prepare_units_migration.haml delete mode 100644 app/views/articles/shared.js.haml create mode 100644 app/views/shared/_article_unit_ratio.haml rename app/views/shared/{_article_price_info.html.haml => _article_version_info.html.haml} (100%) create mode 100644 app/views/shared/js_templates/_unit_conversion_popover_template.haml create mode 100644 app/views/supplier_shares/_share_link.html.haml create mode 100644 app/views/supplier_shares/update.js.haml create mode 100644 app/views/suppliers/_import_search_results.haml create mode 100644 app/views/suppliers/remote_articles.js.haml delete mode 100644 app/views/suppliers/shared_suppliers.html.haml create mode 100644 config/database_shared.yml.MYSQL_SAMPLE create mode 100644 config/units-of-measure/locales/unece_de.yml create mode 100644 config/units-of-measure/locales/unece_en.yml create mode 100644 config/units-of-measure/translations_piece.csv create mode 100644 config/units-of-measure/translations_scalar-de.csv create mode 100644 config/units-of-measure/translations_scalar-en.csv create mode 100644 config/units-of-measure/un-ece-20-remastered.yml create mode 100644 config/units-of-measure/un-ece-20.yml create mode 100644 config/units-of-measure/un-ece-21.yml create mode 100644 db/migrate/20221026102300_alter_articles_add_versioning.rb create mode 100644 db/migrate/20221026102301_alter_articles_add_more_unit_logic.rb create mode 100644 db/migrate/20230414122017_alter_suppliers_sharing_fields.rb create mode 100644 db/migrate/20231104141204_create_article_units.rb create mode 100644 db/migrate/20231210181807_add_unit_migration_completed_to_suppliers.rb create mode 100644 db/seeds/hackathon.seeds.rb create mode 100644 db/seeds/hackathon_de.seeds.rb create mode 100644 db_shared/migrate/20221204112447_create_articles_and_suppliers.rb create mode 100644 db_shared/schema.rb create mode 100644 db_shared/seeds.rb create mode 100644 lib/article_units_lib.rb create mode 100644 lib/tasks/db_shared.rake create mode 100644 lib/tasks/temp_export_supplier.rake create mode 100644 lib/tasks/units.rake mode change 100644 => 100755 plugins/discourse/Rakefile mode change 100644 => 100755 plugins/documents/Rakefile mode change 100644 => 100755 plugins/links/Rakefile mode change 100644 => 100755 plugins/messages/Rakefile mode change 100644 => 100755 plugins/polls/Rakefile mode change 100644 => 100755 plugins/printer/Rakefile mode change 100644 => 100755 plugins/wiki/Rakefile create mode 100644 spec/factories/article_category.rb create mode 100644 spec/factories/article_unit_ratio.rb create mode 100644 spec/factories/article_version.rb create mode 100644 spec/lib/article_units_lib_spec.rb delete mode 100644 spec/support/shared_database.rb diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..e1dce34a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + "env": { + "browser": true, + "jquery": true, + "es6": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 6 + }, + "rules": { + "no-unused-vars": ["error", { "vars": "local" }] + } +} diff --git a/.rubocop.yml b/.rubocop.yml index 42be2b6c..9a529428 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,6 +3,8 @@ inherit_from: .rubocop_todo.yml require: - rubocop-rails - rubocop-rspec + - rubocop-capybara + - rubocop-factory_bot AllCops: NewCops: enable diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9401bdbd..1d011fa1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,37 +1,60 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-02-05 12:00:00 UTC using RuboCop version 1.25.1. +# on 2024-03-15 10:33:02 UTC using RuboCop version 1.60.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 28 -# Cop supports --auto-correct. -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. -# Include: **/*.gemfile, **/Gemfile, **/gems.rb -Bundler/OrderedGems: +# Offense count: 13 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: link_or_button, strict +Capybara/ClickLinkOrButtonStyle: Exclude: - - 'Gemfile' + - 'spec/integration/balancing_spec.rb' -# Offense count: 9 -# Cop supports --auto-correct. +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: DefaultSelector. +Capybara/RSpec/HaveSelector: + Exclude: + - 'spec/integration/product_distribution_example_spec.rb' + +# Offense count: 4 +Capybara/SpecificMatcher: + Exclude: + - 'spec/integration/login_spec.rb' + - 'spec/integration/session_spec.rb' + +# Offense count: 5 # Configuration parameters: Include. -# Include: **/*.gemspec -Gemspec/RequireMFA: +# Include: **/*_spec.rb, **/spec/**/*, **/test/**/*, **/features/support/factories/**/*.rb +FactoryBot/FactoryAssociationWithStrategy: + Exclude: + - 'spec/factories/group_order.rb' + - 'spec/factories/invoice.rb' + - 'spec/factories/order.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Include. +# Include: **/*_spec.rb, **/spec/**/*, **/test/**/*, **/features/support/factories/**/*.rb +FactoryBot/SyntaxMethods: + Exclude: + - 'spec/integration/articles_spec.rb' + - 'spec/models/article_spec.rb' + +# Offense count: 2 +# Configuration parameters: EnforcedStyle, AllowedGems, Include. +# SupportedStyles: Gemfile, gems.rb, gemspec +# Include: **/*.gemspec, **/Gemfile, **/gems.rb +Gemspec/DevelopmentDependencies: Exclude: - - 'plugins/current_orders/foodsoft_current_orders.gemspec' - - 'plugins/discourse/foodsoft_discourse.gemspec' - - 'plugins/documents/foodsoft_documents.gemspec' - - 'plugins/links/foodsoft_links.gemspec' - 'plugins/messages/foodsoft_messages.gemspec' - - 'plugins/polls/foodsoft_polls.gemspec' - - 'plugins/printer/foodsoft_printer.gemspec' - - 'plugins/uservoice/foodsoft_uservoice.gemspec' - 'plugins/wiki/foodsoft_wiki.gemspec' # Offense count: 9 -# Configuration parameters: Include. +# Configuration parameters: Severity, Include. # Include: **/*.gemspec Gemspec/RequiredRubyVersion: Exclude: @@ -45,69 +68,200 @@ Gemspec/RequiredRubyVersion: - 'plugins/uservoice/foodsoft_uservoice.gemspec' - 'plugins/wiki/foodsoft_wiki.gemspec' -# Offense count: 8 -# Configuration parameters: IgnoredMethods. -Lint/AmbiguousBlockAssociation: +# Offense count: 379 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_argument, with_fixed_indentation +Layout/ArgumentAlignment: + Enabled: false + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_element, with_fixed_indentation +Layout/ArrayAlignment: Exclude: - - 'lib/foodsoft/expansion_variables.rb' - - 'spec/api/v1/user/financial_transactions_spec.rb' - - 'spec/api/v1/user/group_order_articles_spec.rb' - - 'spec/models/article_spec.rb' + - 'app/controllers/articles_controller.rb' + - 'app/controllers/stockit_controller.rb' + - 'app/helpers/group_orders_helper.rb' + - 'app/models/article.rb' + - 'app/models/order.rb' + - 'plugins/wiki/app/models/page.rb' -# Offense count: 4 -# Cop supports --auto-correct. -Lint/AmbiguousOperator: +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: IndentationWidth. +Layout/AssignmentIndentation: Exclude: - 'app/mailers/mailer.rb' - - 'spec/models/order_article_spec.rb' + - 'config/environments/production.rb' + - 'lib/article_units_lib.rb' + - 'spec/models/article_spec.rb' + +# Offense count: 54 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith. +# SupportedStylesAlignWith: either, start_of_block, start_of_line +Layout/BlockAlignment: + Enabled: false + +# Offense count: 54 +# This cop supports safe autocorrection (--autocorrect). +Layout/BlockEndNewline: + Enabled: false + +# Offense count: 9 +# This cop supports safe autocorrection (--autocorrect). +Layout/ClosingParenthesisIndentation: + Exclude: + - 'app/documents/order_by_articles.rb' + - 'app/models/group_order.rb' + - 'app/models/group_order_article.rb' + - 'lib/article_units_lib.rb' + - 'lib/order_pdf.rb' # Offense count: 4 -# Cop supports --auto-correct. -Lint/AmbiguousOperatorPrecedence: +# This cop supports safe autocorrection (--autocorrect). +Layout/ElseAlignment: Exclude: - - 'app/models/concerns/price_calculation.rb' - - 'db/seeds/seed_helper.rb' - - 'plugins/messages/app/mail_receivers/messages_mail_receiver.rb' - - 'plugins/wiki/app/helpers/pages_helper.rb' + - 'app/helpers/group_order_articles_helper.rb' + - 'app/helpers/orders_helper.rb' + - 'app/models/group_order.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'app/models/article_category.rb' + - 'app/models/invoice.rb' + - 'app/models/order.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLines: + Exclude: + - 'app/models/invoice.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLinesAroundMethodBody: + Exclude: + - 'app/models/article_category.rb' + - 'app/models/invoice.rb' + - 'app/models/order.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith, Severity. +# SupportedStylesAlignWith: keyword, variable, start_of_line +Layout/EndAlignment: + Exclude: + - 'app/helpers/group_order_articles_helper.rb' + - 'app/helpers/orders_helper.rb' + - 'app/models/group_order.rb' + +# Offense count: 9 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses +Layout/FirstArgumentIndentation: + Exclude: + - 'app/documents/order_by_articles.rb' + - 'app/models/group_order.rb' + - 'app/models/group_order_article.rb' + - 'lib/article_units_lib.rb' + - 'lib/order_pdf.rb' # Offense count: 2 -# Cop supports --auto-correct. -Lint/AmbiguousRegexpLiteral: +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Layout/FirstArrayElementIndentation: + Exclude: + - 'app/controllers/articles_controller.rb' + +# Offense count: 421 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: normal, indented_internal_methods +Layout/IndentationConsistency: Exclude: - 'app/models/article_category.rb' - - 'lib/foodsoft/expansion_variables.rb' + - 'app/models/order.rb' + +# Offense count: 115 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Width, AllowedPatterns. +Layout/IndentationWidth: + Enabled: false + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineArrayBraceLayout: + Exclude: + - 'app/controllers/articles_controller.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineMethodCallBraceLayout: + Exclude: + - 'app/documents/order_by_articles.rb' + - 'app/models/group_order.rb' + - 'app/models/group_order_article.rb' + - 'lib/article_units_lib.rb' + - 'lib/order_pdf.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_parameter, with_fixed_indentation +Layout/ParameterAlignment: + Exclude: + - 'app/helpers/orders_helper.rb' + +# Offense count: 320 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: + Enabled: false # Offense count: 40 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Enabled: false -# Offense count: 3 -# Cop supports --auto-correct. -Lint/BigDecimalNew: +# Offense count: 1 +Lint/BinaryOperatorWithIdenticalOperands: Exclude: - - 'app/models/group_order.rb' - - 'config/initializers/extensions.rb' + - 'app/helpers/orders_helper.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Lint/BooleanSymbol: Exclude: - 'app/models/delivery.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Lint/DeprecatedClassMethods: - Exclude: - - 'config/initializers/secret_token.rb' - - 'lib/tasks/foodsoft_setup.rake' - -# Offense count: 3 +# Offense count: 4 # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. Lint/DuplicateBranch: Exclude: - 'app/controllers/concerns/auth_api.rb' - 'app/controllers/orders_controller.rb' + - 'plugins/wiki/app/controllers/pages_controller.rb' # Offense count: 3 Lint/DuplicateMethods: @@ -123,25 +277,35 @@ Lint/EmptyBlock: - 'spec/factories/group_order_article_quantity.rb' # Offense count: 1 -# Cop supports --auto-correct. +# Configuration parameters: AllowComments. +Lint/EmptyFile: + Exclude: + - 'db_shared/seeds.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). Lint/EnsureReturn: Exclude: - 'app/controllers/finance/bank_accounts_controller.rb' -# Offense count: 2 +# Offense count: 1 +Lint/FloatComparison: + Exclude: + - 'lib/article_units_lib.rb' + +# Offense count: 1 Lint/IneffectiveAccessModifier: Exclude: - 'lib/foodsoft_mail_receiver.rb' - - 'lib/token_verifier.rb' # Offense count: 1 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Lint/InterpolationCheck: Exclude: - 'db/migrate/007_create_article_prices.rb' # Offense count: 1 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Lint/Loop: Exclude: - 'app/models/concerns/mark_as_deleted_with_name.rb' @@ -152,47 +316,22 @@ Lint/MixedRegexpCaptureTypes: - 'lib/bank_transaction_reference.rb' - 'lib/foodsoft_mail_receiver.rb' -# Offense count: 1 -# Cop supports --auto-correct-all. +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). Lint/NonDeterministicRequireOrder: Exclude: + - 'lib/tasks/seeds.rake' - 'spec/spec_helper.rb' -# Offense count: 5 -# Cop supports --auto-correct. -Lint/ParenthesesAsGroupedExpression: - Exclude: - - 'spec/integration/articles_spec.rb' - - 'spec/lib/token_verifier_spec.rb' - - 'spec/models/order_article_spec.rb' - # Offense count: 1 Lint/ReturnInVoidContext: Exclude: - 'lib/foodsoft_config.rb' -# Offense count: 8 -# Cop supports --auto-correct. -Lint/ScriptPermission: - Exclude: - - 'Rakefile' - - 'plugins/discourse/Rakefile' - - 'plugins/documents/Rakefile' - - 'plugins/links/Rakefile' - - 'plugins/messages/Rakefile' - - 'plugins/polls/Rakefile' - - 'plugins/printer/Rakefile' - - 'plugins/wiki/Rakefile' - -# Offense count: 7 -# Cop supports --auto-correct. -Lint/SendWithMixinArgument: +# Offense count: 1 +Lint/SelfAssignment: Exclude: - - 'config/initializers/doorkeeper.rb' - - 'plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb' - - 'plugins/messages/lib/foodsoft_messages/user_link.rb' - - 'plugins/uservoice/lib/foodsoft_uservoice.rb' - - 'plugins/wiki/lib/foodsoft_wiki/mailer.rb' + - 'db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb' # Offense count: 1 # Configuration parameters: IgnoreImplicitReferences. @@ -200,7 +339,7 @@ Lint/ShadowedArgument: Exclude: - 'app/helpers/deliveries_helper.rb' -# Offense count: 8 +# Offense count: 6 Lint/ShadowingOuterLocalVariable: Exclude: - 'app/documents/order_matrix.rb' @@ -209,9 +348,8 @@ Lint/ShadowingOuterLocalVariable: - 'app/models/group_order_article.rb' - 'plugins/discourse/app/controllers/discourse_login_controller.rb' - 'plugins/polls/app/controllers/polls_controller.rb' - - 'spec/integration/config_spec.rb' -# Offense count: 2 +# Offense count: 3 # Configuration parameters: AllowComments, AllowNil. Lint/SuppressedException: Exclude: @@ -225,57 +363,14 @@ Lint/UnderscorePrefixedVariableName: Exclude: - 'app/models/order_article.rb' -# Offense count: 16 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Exclude: - - 'app/models/article.rb' - - 'app/models/group_order.rb' - - 'config/initializers/exception_notification.rb' - - 'plugins/printer/lib/foodsoft_printer/engine.rb' - - 'plugins/uservoice/lib/foodsoft_uservoice.rb' - - 'plugins/wiki/lib/foodsoft_wiki/wiki_parser.rb' - - 'spec/factories/supplier.rb' - - 'spec/factories/user.rb' - - 'spec/integration/config_spec.rb' - - 'spec/models/article_spec.rb' - -# Offense count: 23 -# Cop supports --auto-correct. -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. -Lint/UnusedMethodArgument: - Exclude: - - 'app/controllers/api/v1/base_controller.rb' - - 'app/controllers/concerns/foodcoop_scope.rb' - - 'app/helpers/application_helper.rb' - - 'app/models/article.rb' - - 'app/models/article_category.rb' - - 'app/models/financial_transaction.rb' - - 'app/models/group_order.rb' - - 'app/models/group_order_article.rb' - - 'app/models/order.rb' - - 'app/models/order_article.rb' - - 'app/models/supplier.rb' - - 'lib/foodsoft_mail_receiver.rb' - - 'lib/order_txt.rb' - - 'lib/render_pdf.rb' - - 'plugins/wiki/lib/foodsoft_wiki/mailer.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. -Lint/UselessAccessModifier: - Exclude: - - 'lib/token_verifier.rb' - - 'plugins/messages/app/models/messagegroup.rb' - -# Offense count: 14 +# Offense count: 17 +# This cop supports unsafe autocorrection (--autocorrect-all). Lint/UselessAssignment: Exclude: - 'app/controllers/admin/ordergroups_controller.rb' - 'app/helpers/admin/configs_helper.rb' - 'app/inputs/date_picker_time_input.rb' + - 'app/models/article.rb' - 'app/models/order_article.rb' - 'db/migrate/003_create_suppliers.rb' - 'db/migrate/004_create_article_meta.rb' @@ -285,56 +380,51 @@ Lint/UselessAssignment: - 'plugins/current_orders/app/documents/multiple_orders_by_articles.rb' - 'plugins/current_orders/app/documents/multiple_orders_by_groups.rb' - 'spec/lib/foodsoft_config_spec.rb' + - 'spec/models/article_spec.rb' -# Offense count: 1 -# Configuration parameters: CheckForMethodsWithNoSideEffects. -Lint/Void: - Exclude: - - 'lib/foodsoft_config.rb' - -# Offense count: 160 -# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. +# Offense count: 183 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 143 + Max: 145 -# Offense count: 73 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. -# IgnoredMethods: refine +# Offense count: 27 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. +# AllowedMethods: refine Metrics/BlockLength: - Max: 374 + Max: 222 # Offense count: 6 # Configuration parameters: CountBlocks. Metrics/BlockNesting: - Max: 5 + Max: 4 -# Offense count: 18 +# Offense count: 21 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 288 + Max: 335 -# Offense count: 51 -# Configuration parameters: IgnoredMethods. +# Offense count: 63 +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: Max: 22 -# Offense count: 158 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# Offense count: 187 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 112 + Max: 115 # Offense count: 4 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Max: 190 + Max: 194 # Offense count: 1 # Configuration parameters: CountKeywordArgs, MaxOptionalParameters. Metrics/ParameterLists: Max: 6 -# Offense count: 36 -# Configuration parameters: IgnoredMethods. +# Offense count: 48 +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: Max: 23 @@ -353,15 +443,16 @@ Naming/BlockParameterName: - 'db/migrate/008_create_orders.rb' # Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: Exclude: - 'plugins/messages/app/models/message.rb' -# Offense count: 19 +# Offense count: 18 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to +# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: Exclude: - 'app/controllers/api/v1/base_controller.rb' @@ -374,7 +465,6 @@ Naming/MethodParameterName: - 'spec/integration/config_spec.rb' - 'spec/integration/receive_spec.rb' - 'spec/models/order_article_spec.rb' - - 'spec/support/shared_database.rb' # Offense count: 11 # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros. @@ -394,14 +484,8 @@ Naming/PredicateName: - 'app/serializers/order_serializer.rb' - 'plugins/messages/app/models/message.rb' -# Offense count: 45 -# Cop supports --auto-correct. -# Configuration parameters: PreferredName. -Naming/RescuedExceptionsVariableName: - Enabled: false - # Offense count: 22 -# Configuration parameters: EnforcedStyle, AllowedIdentifiers. +# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, camelCase Naming/VariableName: Exclude: @@ -411,9 +495,9 @@ Naming/VariableName: - 'lib/bank_account_information_importer.rb' # Offense count: 23 -# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer -# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 Naming/VariableNumber: Exclude: - 'app/documents/order_matrix.rb' @@ -433,7 +517,7 @@ RSpec/BeforeAfterAll: - 'spec/lib/foodsoft_mail_receiver_spec.rb' # Offense count: 9 -# Cop supports --auto-correct. +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnabledMethods. RSpec/Capybara/FeatureMethods: Exclude: @@ -448,7 +532,7 @@ RSpec/Capybara/FeatureMethods: - 'spec/integration/supplier_spec.rb' # Offense count: 27 -# Configuration parameters: Prefixes. +# Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: Exclude: @@ -457,18 +541,25 @@ RSpec/ContextWording: - 'spec/models/order_article_spec.rb' - 'spec/models/supplier_spec.rb' -# Offense count: 1 +# Offense count: 7 # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: Exclude: - 'spec/api/v1/swagger_spec.rb' + - 'spec/integration/balancing_spec.rb' + - 'spec/integration/config_spec.rb' + - 'spec/integration/product_distribution_example_spec.rb' + - 'spec/integration/receive_spec.rb' + - 'spec/integration/session_spec.rb' + - 'spec/integration/supplier_spec.rb' -# Offense count: 101 -# Cop supports --auto-correct. +# Offense count: 103 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle. # SupportedStyles: described_class, explicit RSpec/DescribedClass: Exclude: + - 'spec/integration/order_spec.rb' - 'spec/lib/bank_transaction_reference_spec.rb' - 'spec/lib/foodsoft_config_spec.rb' - 'spec/lib/foodsoft_mail_receiver_spec.rb' @@ -479,113 +570,51 @@ RSpec/DescribedClass: - 'spec/models/ordergroup_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 9 -# Cop supports --auto-correct. +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowConsecutiveOneLiners. RSpec/EmptyLineAfterExample: Exclude: - - 'spec/integration/session_spec.rb' - - 'spec/models/user_spec.rb' + - 'spec/api/v1/swagger_spec.rb' -# Offense count: 6 -# Cop supports --auto-correct. -RSpec/EmptyLineAfterExampleGroup: - Exclude: - - 'spec/models/order_article_spec.rb' +# Offense count: 71 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 81 -# Offense count: 30 -# Cop supports --auto-correct. -RSpec/EmptyLineAfterFinalLet: +# Offense count: 7 +# Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly. +# Include: **/*_spec*rb*, **/spec/**/* +RSpec/FilePath: Exclude: - 'spec/api/v1/order_articles_spec.rb' - - 'spec/api/v1/swagger_spec.rb' - 'spec/api/v1/user/financial_transactions_spec.rb' - 'spec/api/v1/user/group_order_articles_spec.rb' + - 'spec/api/v1/user/ordergroup_spec.rb' - 'spec/integration/articles_spec.rb' - - 'spec/integration/balancing_spec.rb' - 'spec/integration/login_spec.rb' - - 'spec/integration/supplier_spec.rb' - - 'spec/models/order_article_spec.rb' - - 'spec/models/order_spec.rb' - - 'spec/models/user_spec.rb' - - 'spec/support/api_helper.rb' + - 'spec/lib/bank_account_information_importer_spec.rb' -# Offense count: 17 -# Cop supports --auto-correct. -RSpec/EmptyLineAfterHook: +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: single_line_only, single_statement_only, disallow, require_implicit +RSpec/ImplicitSubject: + Exclude: + - 'spec/api/v1/swagger_spec.rb' + +# Offense count: 59 +# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. +RSpec/IndexedLet: Exclude: - 'spec/api/v1/swagger_spec.rb' - 'spec/api/v1/user/financial_transactions_spec.rb' - - 'spec/api/v1/user/group_order_articles_spec.rb' + - 'spec/api/v1/user/ordergroup_spec.rb' - 'spec/integration/balancing_spec.rb' - - 'spec/integration/login_spec.rb' + - 'spec/integration/receive_spec.rb' + - 'spec/models/bank_transaction_spec.rb' - 'spec/models/order_article_spec.rb' - - 'spec/support/api_oauth.rb' - -# Offense count: 59 -# Configuration parameters: CountAsOne. -RSpec/ExampleLength: - Max: 81 - -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: CustomTransform, IgnoredWords. -RSpec/ExampleWording: - Exclude: - - 'spec/models/order_spec.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: Include. -# Include: spec/factories.rb, spec/factories/**/*.rb, features/support/factories/**/*.rb -RSpec/FactoryBot/FactoryClassName: - Exclude: - - 'spec/factories/article.rb' - - 'spec/factories/doorkeeper.rb' - - 'spec/factories/supplier.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -RSpec/FactoryBot/SyntaxMethods: - Exclude: - - 'spec/integration/articles_spec.rb' - - 'spec/models/article_spec.rb' - -# Offense count: 7 -# Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly. -# Include: **/*_spec*rb*, **/spec/**/* -RSpec/FilePath: - Exclude: - - 'spec/api/v1/order_articles_spec.rb' - - 'spec/api/v1/user/financial_transactions_spec.rb' - - 'spec/api/v1/user/group_order_articles_spec.rb' - - 'spec/api/v1/user/ordergroup_spec.rb' - - 'spec/integration/articles_spec.rb' - - 'spec/integration/login_spec.rb' - - 'spec/lib/bank_account_information_importer_spec.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, each, example -RSpec/HookArgument: - Exclude: - - 'spec/spec_helper.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -RSpec/HooksBeforeExamples: - Exclude: - - 'spec/integration/balancing_spec.rb' - - 'spec/lib/foodsoft_mail_receiver_spec.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: single_line_only, single_statement_only, disallow -RSpec/ImplicitSubject: - Exclude: - - 'spec/api/v1/swagger_spec.rb' + - 'spec/models/ordergroup_spec.rb' # Offense count: 6 # Configuration parameters: AssignmentOnly. @@ -599,12 +628,6 @@ RSpec/IteratedExpectation: - 'spec/models/order_spec.rb' - 'spec/models/supplier_spec.rb' -# Offense count: 3 -# Cop supports --auto-correct. -RSpec/LetBeforeExamples: - Exclude: - - 'spec/api/v1/swagger_spec.rb' - # Offense count: 13 RSpec/LetSetup: Exclude: @@ -622,38 +645,29 @@ RSpec/MissingExampleGroupArgument: - 'spec/models/group_order_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 87 +# Offense count: 89 RSpec/MultipleExpectations: Max: 22 -# Offense count: 80 +# Offense count: 82 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: Max: 17 +# Offense count: 7 +# Configuration parameters: EnforcedStyle, IgnoreSharedExamples. +# SupportedStyles: always, named_only +RSpec/NamedSubject: + Exclude: + - 'spec/api/v1/swagger_spec.rb' + # Offense count: 29 +# Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 6 -# Offense count: 31 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: not_to, to_not -RSpec/NotToNot: - Exclude: - - 'spec/api/v1/user/financial_transactions_spec.rb' - - 'spec/api/v1/user/group_order_articles_spec.rb' - - 'spec/integration/balancing_spec.rb' - - 'spec/integration/login_spec.rb' - - 'spec/integration/receive_spec.rb' - - 'spec/integration/session_spec.rb' - - 'spec/lib/token_verifier_spec.rb' - - 'spec/models/article_spec.rb' - - 'spec/models/order_spec.rb' - - 'spec/models/supplier_spec.rb' - # Offense count: 8 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. # SupportedStyles: inflected, explicit RSpec/PredicateMatcher: @@ -661,6 +675,24 @@ RSpec/PredicateMatcher: - 'spec/models/article_spec.rb' - 'spec/models/user_spec.rb' +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +RSpec/Rails/HaveHttpStatus: + Exclude: + - 'spec/api/v1/user/financial_transactions_spec.rb' + - 'spec/api/v1/user/group_order_articles_spec.rb' + +# Offense count: 7 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: not_to, be_invalid +RSpec/Rails/NegationBeValid: + Exclude: + - 'spec/models/article_spec.rb' + - 'spec/models/order_spec.rb' + - 'spec/models/supplier_spec.rb' + - 'spec/models/user_spec.rb' + # Offense count: 6 RSpec/RepeatedDescription: Exclude: @@ -674,20 +706,18 @@ RSpec/RepeatedExample: - 'spec/lib/bank_transaction_reference_spec.rb' - 'spec/lib/foodsoft_mail_receiver_spec.rb' -# Offense count: 9 -# Cop supports --auto-correct. -RSpec/ScatteredLet: - Exclude: - - 'spec/api/v1/swagger_spec.rb' - - 'spec/api/v1/user/group_order_articles_spec.rb' - - 'spec/api/v1/user/ordergroup_spec.rb' - # Offense count: 7 -RSpec/ScatteredSetup: +# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. +# Include: **/*_spec.rb +RSpec/SpecFilePathFormat: Exclude: + - 'spec/api/v1/order_articles_spec.rb' + - 'spec/api/v1/user/financial_transactions_spec.rb' + - 'spec/api/v1/user/group_order_articles_spec.rb' - 'spec/api/v1/user/ordergroup_spec.rb' - - 'spec/integration/balancing_spec.rb' + - 'spec/integration/articles_spec.rb' - 'spec/integration/login_spec.rb' + - 'spec/lib/bank_account_information_importer_spec.rb' # Offense count: 1 # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. @@ -695,37 +725,19 @@ RSpec/VerifiedDoubles: Exclude: - 'spec/support/api_oauth.rb' -# Offense count: 45 -# Cop supports --auto-correct. +# Offense count: 44 +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/ActiveRecordAliases: Enabled: false -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/ActiveRecordCallbacksOrder: - Exclude: - - 'app/models/financial_transaction_type.rb' - - 'app/models/order.rb' - - 'app/models/stock_change.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: Include. -# Include: db/migrate/*.rb -Rails/AddColumnIndex: - Exclude: - - 'db/migrate/20171002000000_create_financial_links.rb' - # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/ApplicationMailer: Exclude: - 'app/mailers/mailer.rb' # Offense count: 20 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/ApplicationRecord: Exclude: - 'app/models/supplier_category.rb' @@ -744,26 +756,43 @@ Rails/ApplicationRecord: - 'plugins/printer/app/models/printer_job.rb' - 'plugins/printer/app/models/printer_job_update.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: NilOrEmpty, NotPresent, UnlessPresent. -Rails/Blank: - Exclude: - - 'app/controllers/api/v1/base_controller.rb' +# Offense count: 33 +# Configuration parameters: Database, Include. +# SupportedDatabases: mysql, postgresql +# Include: db/**/*.rb +Rails/BulkChangeTable: + Exclude: + - 'db/migrate/20130624085246_remove_weekly_task_from_groups.rb' + - 'db/migrate/20130930132511_add_quantities_to_order_article.rb' + - 'db/migrate/20150227161931_add_replyto_and_groupid_to_messages.rb' + - 'db/migrate/20160217164552_add_break_to_group.rb' + - 'db/migrate/20160218151041_add_attachment_to_invoice.rb' + - 'db/migrate/20160226000000_add_email_to_message.foodsoft_messages_engine.rb' + - 'db/migrate/20160309153440_create_doorkeeper_tables.rb' + - 'db/migrate/20171002000000_create_financial_links.rb' + - 'db/migrate/20171201000000_add_name_short_to_financial_transaction_type.rb' + - 'db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb' + - 'db/migrate/20181201000200_add_deleted_to_financial_transaction_type.rb' + - 'db/migrate/20221026102300_alter_articles_add_versioning.rb' + - 'db/migrate/20221026102301_alter_articles_add_more_unit_logic.rb' + - 'db/migrate/20230414122017_alter_suppliers_sharing_fields.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Rails/ContentTag: +# Offense count: 4 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/CompactBlank: Exclude: - - 'plugins/wiki/app/helpers/pages_helper.rb' + - 'app/controllers/orders_controller.rb' + - 'app/helpers/order_articles_helper.rb' + - 'spec/integration/config_spec.rb' -# Offense count: 33 +# Offense count: 35 # Configuration parameters: Include. -# Include: db/migrate/*.rb +# Include: db/**/*.rb Rails/CreateTableWithTimestamps: Enabled: false # Offense count: 5 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowToTime. # SupportedStyles: strict, flexible Rails/Date: @@ -774,47 +803,19 @@ Rails/Date: - 'spec/integration/order_spec.rb' - 'spec/models/order_spec.rb' -# Offense count: 66 -# Cop supports --auto-correct-all. +# Offense count: 70 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Whitelist, AllowedMethods, AllowedReceivers. -# Whitelist: find_by_sql -# AllowedMethods: find_by_sql -# AllowedReceivers: Gem::Specification +# Whitelist: find_by_sql, find_by_token_for +# AllowedMethods: find_by_sql, find_by_token_for +# AllowedReceivers: Gem::Specification, page Rails/DynamicFindBy: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -Rails/EagerEvaluationLogMessage: - Exclude: - - 'db/migrate/20130718183101_migrate_user_settings.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/EnumHash: - Exclude: - - 'app/models/order.rb' - -# Offense count: 9 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: slashes, arguments -Rails/FilePath: - Exclude: - - 'config/application.rb' - - 'config/initializers/secret_token.rb' - - 'lib/order_txt.rb' - - 'lib/render_csv.rb' - - 'lib/render_pdf.rb' - - 'plugins/current_orders/app/documents/multiple_orders_by_groups.rb' - - 'spec/api/v1/swagger_spec.rb' - -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: Include, IgnoredMethods. -# Include: app/models/**/*.rb -# IgnoredMethods: order, limit, select, lock +# Offense count: 19 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods, AllowedPatterns. +# AllowedMethods: order, limit, select, lock Rails/FindEach: Exclude: - 'app/models/bank_account.rb' @@ -822,21 +823,28 @@ Rails/FindEach: - 'app/models/ordergroup.rb' - 'app/models/periodic_task_group.rb' - 'app/models/task.rb' + - 'db/migrate/20090120184410_road_to_version_three.rb' + - 'db/migrate/20090731132547_add_stats_to_groups.rb' + - 'db/migrate/20130622095040_move_weekly_tasks.rb' + - 'db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb' + - 'db/migrate/20210205090257_introduce_received_state_in_orders.rb' + - 'db/seeds/seed_helper.rb' + - 'lib/financial_transactions_csv.rb' + - 'lib/order_txt.rb' -# Offense count: 26 +# Offense count: 24 # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/HasManyOrHasOneDependent: Exclude: - 'app/models/article.rb' - 'app/models/article_category.rb' - - 'app/models/article_price.rb' + - 'app/models/article_version.rb' - 'app/models/financial_link.rb' - 'app/models/financial_transaction.rb' - 'app/models/group_order.rb' - 'app/models/order.rb' - 'app/models/ordergroup.rb' - - 'app/models/shared_supplier.rb' - 'app/models/stock_article.rb' - 'app/models/supplier.rb' - 'app/models/supplier_category.rb' @@ -852,27 +860,20 @@ Rails/HelperInstanceVariable: - 'app/helpers/application_helper.rb' - 'app/helpers/orders_helper.rb' -# Offense count: 14 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: numeric, symbolic -Rails/HttpStatus: +# Offense count: 1 +Rails/I18nLocaleTexts: Exclude: - - 'app/controllers/admin/bank_accounts_controller.rb' - - 'app/controllers/admin/financial_transaction_classes_controller.rb' - - 'app/controllers/admin/financial_transaction_types_controller.rb' - - 'app/controllers/api/v1/base_controller.rb' - - 'app/controllers/styles_controller.rb' - - 'plugins/links/app/controllers/links_controller.rb' + - 'plugins/messages/app/controllers/messages_controller.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Rails/IndexBy: +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +Rails/IndexWith: Exclude: - - 'app/models/order.rb' - - 'spec/api/v1/user/ordergroup_spec.rb' + - 'app/helpers/application_helper.rb' + - 'lib/foodsoft_config.rb' + - 'plugins/wiki/app/controllers/pages_controller.rb' -# Offense count: 23 +# Offense count: 20 # Configuration parameters: IgnoreScopes, Include. # Include: app/models/**/*.rb Rails/InverseOf: @@ -884,8 +885,6 @@ Rails/InverseOf: - 'app/models/invoice.rb' - 'app/models/mail_delivery_status.rb' - 'app/models/order.rb' - - 'app/models/shared_article.rb' - - 'app/models/shared_supplier.rb' - 'app/models/stock_change.rb' - 'app/models/supplier.rb' - 'app/models/task.rb' @@ -894,28 +893,21 @@ Rails/InverseOf: # Offense count: 2 # Configuration parameters: Include. -# Include: app/controllers/**/*.rb +# Include: app/controllers/**/*.rb, app/mailers/**/*.rb Rails/LexicallyScopedActionFilter: Exclude: - 'app/controllers/group_orders_controller.rb' - 'app/controllers/suppliers_controller.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Rails/LinkToBlank: - Exclude: - - 'app/helpers/application_helper.rb' - -# Offense count: 3 -# Cop supports --auto-correct-all. +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/NegateInclude: Exclude: - 'app/helpers/application_helper.rb' - - 'app/models/supplier.rb' - 'lib/tasks/foodsoft_setup.rake' # Offense count: 34 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Include. # Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb Rails/Output: @@ -934,7 +926,7 @@ Rails/Output: - 'db/migrate/20090120184410_road_to_version_three.rb' - 'db/migrate/20130622095040_move_weekly_tasks.rb' -# Offense count: 28 +# Offense count: 25 Rails/OutputSafety: Exclude: - 'app/helpers/admin/configs_helper.rb' @@ -946,147 +938,169 @@ Rails/OutputSafety: - 'plugins/wiki/app/helpers/pages_helper.rb' # Offense count: 1 -# Cop supports --auto-correct. -Rails/Pluck: +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/Pick: Exclude: - - 'lib/ordergroups_csv.rb' + - 'app/controllers/finance/financial_links_controller.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Rails/PluralizationGrammar: +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/Pluck: Exclude: - - 'app/controllers/application_controller.rb' - - 'lib/tasks/foodsoft.rake' + - 'app/controllers/articles_controller.rb' + - 'lib/ordergroups_csv.rb' + - 'spec/api/v1/order_articles_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. -Rails/Presence: +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: conservative, aggressive +Rails/PluckInWhere: Exclude: - - 'db/migrate/021_remove_table_article_prices.rb' - -# Offense count: 36 -# Cop supports --auto-correct. -# Configuration parameters: NotNilAndNotEmpty, NotBlank, UnlessBlank. -Rails/Present: - Enabled: false + - 'db/migrate/20140921104907_remove_stale_memberships.rb' -# Offense count: 6 -# Cop supports --auto-correct-all. +# Offense count: 9 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Include. # Include: **/Rakefile, **/*.rake Rails/RakeEnvironment: Exclude: + - 'lib/tasks/db_shared.rake' - 'lib/tasks/foodsoft_setup.rake' - 'lib/tasks/resque.rake' -# Offense count: 3 -# Cop supports --auto-correct. -Rails/RedundantForeignKey: +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedReceivers. +# AllowedReceivers: ActionMailer::Preview, ActiveSupport::TimeZone +Rails/RedundantActiveRecordAllMethod: Exclude: - - 'app/models/financial_transaction.rb' - - 'plugins/messages/app/models/message.rb' + - 'db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb' -# Offense count: 1 -# Cop supports --auto-correct. +# Offense count: 14 +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/RedundantPresenceValidationOnBelongsTo: Exclude: + - 'app/models/article_version.rb' + - 'app/models/bank_transaction.rb' + - 'app/models/delivery.rb' + - 'app/models/financial_transaction.rb' - 'app/models/financial_transaction_type.rb' + - 'app/models/group_order.rb' + - 'app/models/group_order_article.rb' + - 'app/models/group_order_article_quantity.rb' + - 'app/models/invite.rb' + - 'app/models/invoice.rb' + - 'app/models/order_article.rb' + - 'app/models/order_comment.rb' + - 'app/models/stock_change.rb' + +# Offense count: 4 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/RootPathnameMethods: + Exclude: + - 'config/application.rb' + - 'lib/tasks/foodsoft_setup.rake' + - 'lib/tasks/seeds.rake' # Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: ConvertTry. -Rails/SafeNavigation: +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/SelectMap: Exclude: - - 'app/models/group_order_article.rb' + - 'lib/foodsoft/expansion_variables.rb' -# Offense count: 63 +# Offense count: 71 # Configuration parameters: ForbiddenMethods, AllowedMethods. # ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all Rails/SkipsModelValidations: Enabled: false # Offense count: 3 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/SquishedSQLHeredocs: Exclude: - 'app/controllers/finance/financial_links_controller.rb' - 'app/models/financial_link.rb' - 'db/migrate/20181201000305_ensure_article_for_article_price.rb' -# Offense count: 34 -# Cop supports --auto-correct. +# Offense count: 10 +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/ThreeStateBooleanColumn: + Exclude: + - 'db/migrate/014_create_tasks.rb' + - 'db/migrate/20090120184410_road_to_version_three.rb' + - 'db/migrate/20090811144901_add_weekly_to_tasks.rb' + - 'db/migrate/20120929155541_add_ignore_apple_restriction_to_groups.rb' + - 'db/migrate/20121216180646_remove_assigned_from_tasks.rb' + - 'db/migrate/20130624084223_remove_weekly_from_tasks.rb' + - 'db/migrate/20130624085246_remove_weekly_task_from_groups.rb' + +# Offense count: 39 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: strict, flexible Rails/TimeZone: Enabled: false -# Offense count: 3 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/ToSWithArgument: + Exclude: + - 'plugins/wiki/app/views/pages/all.rss.builder' + +# Offense count: 1 +# Configuration parameters: TransactionMethods. +Rails/TransactionExitStatement: + Exclude: + - 'app/models/bank_transaction.rb' + +# Offense count: 8 # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/UniqueValidationWithoutIndex: Exclude: - 'app/models/bank_account.rb' + - 'app/models/financial_transaction_class.rb' + - 'app/models/financial_transaction_type.rb' + - 'app/models/supplier.rb' - 'app/models/supplier_category.rb' + - 'app/models/user.rb' # Offense count: 2 -# Configuration parameters: Environments. +# Configuration parameters: Severity, Environments. # Environments: development, test, production Rails/UnknownEnv: Exclude: - 'config/initializers/gaffe.rb' - 'config/initializers/secret_token.rb' -# Offense count: 68 -# Cop supports --auto-correct. -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/Validation: - Enabled: false - # Offense count: 2 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Rails/WhereEquals: Exclude: - 'app/controllers/finance/invoices_controller.rb' - 'app/models/financial_transaction.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: exists, where Rails/WhereExists: Exclude: - 'app/models/concerns/mark_as_deleted_with_name.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Rails/WhereNot: - Exclude: - - 'db/migrate/20140921104907_remove_stale_memberships.rb' - - 'db/migrate/20210205090257_introduce_received_state_in_orders.rb' - -# Offense count: 5 -# Cop supports --auto-correct. +# Offense count: 4 +# This cop supports unsafe autocorrection (--autocorrect-all). Security/YAMLLoad: Exclude: - 'app/controllers/finance/bank_accounts_controller.rb' - 'db/migrate/20130718183101_migrate_user_settings.rb' - 'db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb' - 'lib/foodsoft_config.rb' - - 'spec/api/v1/swagger_spec.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: prefer_alias, prefer_alias_method -Style/Alias: - Exclude: - - 'config/initializers/session_store.rb' - - 'plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb' - - 'plugins/printer/lib/foodsoft_printer/order_printer_jobs.rb' # Offense count: 4 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, conditionals Style/AndOr: @@ -1096,68 +1110,30 @@ Style/AndOr: - 'plugins/documents/app/controllers/documents_controller.rb' - 'spec/support/coverage.rb' -# Offense count: 19 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# Offense count: 50 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch -# IgnoredMethods: lambda, proc, it +# AllowedMethods: lambda, proc, it Style/BlockDelimiters: - Exclude: - - 'app/controllers/api/v1/user/ordergroup_controller.rb' - - 'app/helpers/group_orders_helper.rb' - - 'app/helpers/orders_helper.rb' - - 'app/models/order.rb' - - 'db/migrate/008_create_orders.rb' - - 'lib/tasks/resque.rake' - - 'spec/api/v1/user/group_order_articles_spec.rb' - - 'spec/factories/user.rb' - - 'spec/lib/foodsoft_mail_receiver_spec.rb' - - 'spec/support/coverage.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowOnConstant. -Style/CaseEquality: + Enabled: false + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowOnConstant, AllowOnSelfClass. +Style/CaseEquality: Exclude: - 'lib/tasks/foodsoft_setup.rake' -# Offense count: 7 -# Cop supports --auto-correct-all. -Style/CaseLikeIf: - Exclude: - - 'app/helpers/admin/configs_helper.rb' - - 'app/helpers/group_orders_helper.rb' - - 'app/models/order.rb' - - 'lib/foodsoft_date_util.rb' - - 'lib/render_pdf.rb' - - 'lib/tasks/foodsoft_setup.rake' - - 'plugins/uservoice/lib/foodsoft_uservoice.rb' - -# Offense count: 53 -# Cop supports --auto-correct. +# Offense count: 55 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: is_a?, kind_of? -Style/ClassCheck: - Exclude: - - 'app/helpers/orders_helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -# IgnoredMethods: ==, equal?, eql? -Style/ClassEqualityComparison: - Exclude: - - 'spec/factories/supplier.rb' - # Offense count: 3 Style/ClassVars: Exclude: @@ -1165,39 +1141,8 @@ Style/ClassVars: - 'lib/foodsoft/expansion_variables.rb' - 'lib/foodsoft_mail_receiver.rb' -# Offense count: 3 -# Cop supports --auto-correct. -Style/ColonMethodCall: - Exclude: - - 'app/models/supplier.rb' - - 'plugins/discourse/app/controllers/discourse_controller.rb' - - 'plugins/messages/app/mail_receivers/messages_mail_receiver.rb' - -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowInnerBackticks. -# SupportedStyles: backticks, percent_x, mixed -Style/CommandLiteral: - Exclude: - - 'lib/tasks/foodsoft_setup.rake' - -# Offense count: 10 -# Cop supports --auto-correct. -# Configuration parameters: Keywords, RequireColon. -# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE -Style/CommentAnnotation: - Exclude: - - 'app/controllers/admin/configs_controller.rb' - - 'app/inputs/delta_input.rb' - - 'app/models/order_article.rb' - - 'app/models/shared_supplier.rb' - - 'config/application.rb' - - 'spec/models/article_spec.rb' - - 'spec/models/order_spec.rb' - - 'spec/support/shared_database.rb' - # Offense count: 12 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/CommentedKeyword: Exclude: - 'app/controllers/deliveries_controller.rb' @@ -1208,106 +1153,29 @@ Style/CommentedKeyword: - 'config/routes.rb' - 'db/migrate/20090120184410_road_to_version_three.rb' -# Offense count: 13 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. -# SupportedStyles: assign_to_condition, assign_inside_condition -Style/ConditionalAssignment: - Exclude: - - 'app/controllers/application_controller.rb' - - 'app/controllers/articles_controller.rb' - - 'app/controllers/concerns/locale.rb' - - 'app/controllers/finance/bank_transactions_controller.rb' - - 'app/controllers/finance/financial_transactions_controller.rb' - - 'app/controllers/home_controller.rb' - - 'app/controllers/orders_controller.rb' - - 'plugins/documents/app/controllers/documents_controller.rb' - - 'plugins/messages/app/mail_receivers/messages_mail_receiver.rb' - - 'plugins/wiki/app/controllers/pages_controller.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/DefWithParentheses: - Exclude: - - 'app/models/user.rb' - -# Offense count: 314 +# Offense count: 348 # Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: allowed_in_returns, forbidden Style/DoubleNegation: Exclude: - 'app/controllers/tasks_controller.rb' -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, AllowComments. # SupportedStyles: empty, nil, both Style/EmptyElse: Exclude: - - 'app/helpers/application_helper.rb' - - 'app/models/article.rb' - - 'app/models/order_article.rb' - - 'app/models/user.rb' - - 'lib/token_verifier.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/EmptyLiteral: - Exclude: - - 'plugins/wiki/app/helpers/pages_helper.rb' - -# Offense count: 14 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: compact, expanded -Style/EmptyMethod: - Exclude: - - 'app/controllers/articles_controller.rb' - - 'app/controllers/feedback_controller.rb' - - 'app/controllers/finance/invoices_controller.rb' - - 'app/controllers/home_controller.rb' - - 'app/controllers/login_controller.rb' - - 'app/mailers/mailer.rb' - - 'db/migrate/024_add_deposit_defaults.rb' - - 'db/migrate/20090120184410_road_to_version_three.rb' - - 'db/migrate/20090907120012_add_missing_indexes.rb' - - 'db/migrate/20130702113610_update_group_order_totals.rb' - - 'db/migrate/20130718183101_migrate_user_settings.rb' - - 'db/migrate/20140318173000_delete_empty_group_order_articles.rb' - - 'lib/bank_account_connector.rb' - -# Offense count: 21 -# Cop supports --auto-correct. -Style/ExpandPathArguments: - Enabled: false - -# Offense count: 7 -# Cop supports --auto-correct. -Style/ExplicitBlockArgument: - Exclude: - - 'app/documents/order_fax.rb' - - 'app/helpers/admin/configs_helper.rb' - - 'app/models/concerns/find_each_with_order.rb' - - 'plugins/current_orders/app/documents/multiple_orders_by_articles.rb' - - 'plugins/current_orders/app/documents/multiple_orders_by_groups.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -Style/FileWrite: - Exclude: - - 'config/initializers/secret_token.rb' - - 'lib/order_txt.rb' - - 'lib/render_csv.rb' - - 'lib/render_pdf.rb' + - 'app/models/group_order.rb' # Offense count: 1 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv Style/FloatDivision: @@ -1315,7 +1183,7 @@ Style/FloatDivision: - 'app/models/ordergroup.rb' # Offense count: 18 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: each, for Style/For: @@ -1331,59 +1199,61 @@ Style/For: - 'plugins/messages/app/mail_receivers/messages_mail_receiver.rb' - 'plugins/wiki/app/views/pages/all.rss.builder' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: format, sprintf, percent -Style/FormatString: - Exclude: - - 'lib/order_txt.rb' - # Offense count: 6 -# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, IgnoredMethods. +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns. # SupportedStyles: annotated, template, unannotated +# AllowedMethods: redirect Style/FormatStringToken: EnforcedStyle: unannotated -# Offense count: 490 -# Cop supports --auto-correct. +# Offense count: 515 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false # Offense count: 3 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/GlobalStdStream: Exclude: - 'config/environments/production.rb' - 'lib/tasks/foodsoft.rake' - 'lib/tasks/foodsoft_setup.rake' -# Offense count: 61 -# Configuration parameters: MinBodyLength. +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: braces, no_braces -Style/HashAsLastArrayItem: Exclude: + - 'app/models/article_category.rb' - 'app/models/order.rb' + - 'plugins/wiki/app/controllers/pages_controller.rb' -# Offense count: 6 -# Cop supports --auto-correct. +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSplatArgument. Style/HashConversion: Exclude: - - 'app/helpers/application_helper.rb' - - 'app/models/article.rb' - - 'app/models/order.rb' - 'lib/bank_account_information_importer.rb' - - 'plugins/wiki/app/controllers/pages_controller.rb' - - 'spec/api/v1/user/ordergroup_spec.rb' + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedReceivers. +# AllowedReceivers: Thread.current +Style/HashEachMethods: + Exclude: + - 'app/controllers/articles_controller.rb' + - 'app/models/group_order.rb' + - 'spec/integration/config_spec.rb' + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/HashExcept: + Exclude: + - 'plugins/uservoice/lib/foodsoft_uservoice.rb' + - 'spec/models/article_spec.rb' # Offense count: 8 # Configuration parameters: MinBranchesCount. @@ -1398,46 +1268,22 @@ Style/HashLikeCase: - 'plugins/documents/app/controllers/documents_controller.rb' - 'plugins/wiki/app/controllers/pages_controller.rb' -# Offense count: 3904 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either -Style/HashSyntax: - Enabled: false - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: AllowIfModifier. -Style/IfInsideElse: - Exclude: - - 'app/models/article.rb' - - 'app/models/task.rb' - - 'lib/apple_bar.rb' - - 'plugins/wiki/app/controllers/pages_controller.rb' - -# Offense count: 60 -# Cop supports --auto-correct. -Style/IfUnlessModifier: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedMethods. # AllowedMethods: nonzero? Style/IfWithBooleanLiteralBranches: Exclude: - 'app/models/order_article.rb' - - 'app/models/task.rb' # Offense count: 1 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/InfiniteLoop: Exclude: - 'lib/order_pdf.rb' # Offense count: 3 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: InverseMethods, InverseBlocks. Style/InverseMethods: Exclude: @@ -1445,473 +1291,182 @@ Style/InverseMethods: - 'app/helpers/deliveries_helper.rb' - 'spec/support/coverage.rb' -# Offense count: 4 -# Cop supports --auto-correct. +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: line_count_dependent, lambda, literal Style/Lambda: Exclude: - - 'app/models/financial_link.rb' - - 'lib/foodsoft_mail_receiver.rb' - - 'plugins/messages/app/models/message.rb' + - 'app/models/article.rb' + - 'app/models/financial_transaction.rb' + - 'app/models/order_article.rb' + - 'app/models/supplier.rb' + - 'spec/api/v1/swagger_spec.rb' # Offense count: 5 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/LineEndConcatenation: Exclude: - 'db/migrate/20130702113610_update_group_order_totals.rb' - 'plugins/current_orders/app/documents/multiple_orders_by_articles.rb' # Offense count: 1 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/MapToHash: Exclude: - - 'lib/foodsoft_config.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -Style/MethodCallWithoutArgsParentheses: - Exclude: - - 'plugins/discourse/app/controllers/discourse_login_controller.rb' + - 'app/models/article_version.rb' -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline -Style/MethodDefParentheses: +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/MinMaxComparison: Exclude: - - 'app/controllers/concerns/send_order_pdf.rb' - - 'app/helpers/application_helper.rb' - - 'app/helpers/finance/invoices_helper.rb' - - 'plugins/discourse/app/controllers/discourse_controller.rb' + - 'app/models/group_order_article.rb' + - 'lib/apple_bar.rb' # Offense count: 1 Style/MixinUsage: Exclude: - 'lib/tasks/foodsoft_setup.rake' -# Offense count: 2 +# Offense count: 3 Style/MultilineBlockChain: Exclude: - 'app/helpers/group_orders_helper.rb' - 'app/models/order.rb' - 'config/initializers/rails6_backports.rb' -# Offense count: 2 -# Cop supports --auto-correct. +# Offense count: 33 +# This cop supports safe autocorrection (--autocorrect). Style/MultilineIfModifier: - Exclude: - - 'app/models/user.rb' - - 'plugins/current_orders/app/controllers/current_orders/ordergroups_controller.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Style/MultilineIfThen: - Exclude: - - 'app/controllers/finance/financial_links_controller.rb' - - 'lib/bank_account_information_importer.rb' - -# Offense count: 12 -# Cop supports --auto-correct. -Style/MultilineWhenThen: - Exclude: - - 'app/controllers/finance/balancing_controller.rb' - - 'app/helpers/application_helper.rb' - - 'app/helpers/finance/balancing_helper.rb' - - 'app/models/order.rb' + Enabled: false -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: AllowMethodComparison. -Style/MultipleComparison: +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +Style/MultilineTernaryOperator: Exclude: - - 'app/models/order.rb' - - 'app/models/order_article.rb' - - 'spec/models/article_spec.rb' + - 'app/helpers/group_order_articles_helper.rb' + - 'app/helpers/orders_helper.rb' + - 'app/models/group_order.rb' # Offense count: 24 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: both, prefix, postfix -Style/NegatedIf: - Exclude: - - 'app/controllers/orders_controller.rb' - - 'app/helpers/articles_helper.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -Style/NegatedIfElseCondition: - Exclude: - - 'app/controllers/articles_controller.rb' - - 'app/controllers/concerns/auth.rb' - - 'app/models/article.rb' - -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: AllowedMethods. -# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with -Style/NestedParenthesizedCalls: - Exclude: - - 'app/models/user.rb' - - 'spec/models/order_article_spec.rb' - -# Offense count: 7 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinBodyLength. -# SupportedStyles: skip_modifier_ifs, always -Style/Next: - Exclude: - - 'app/controllers/finance/financial_transactions_controller.rb' - - 'app/controllers/orders_controller.rb' - - 'app/helpers/orders_helper.rb' - - 'db/migrate/20130622095040_move_weekly_tasks.rb' - - 'lib/tasks/foodsoft.rake' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. +# Offense count: 65 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison -Style/NilComparison: - Exclude: - - 'app/controllers/application_controller.rb' - - 'plugins/wiki/app/helpers/pages_helper.rb' +Style/NumericPredicate: + Enabled: false -# Offense count: 10 -# Cop supports --auto-correct. -Style/Not: +# Offense count: 1 +Style/OpenStructUse: Exclude: - - 'app/controllers/concerns/auth.rb' - - 'app/controllers/orders_controller.rb' - - 'app/helpers/deliveries_helper.rb' - - 'app/models/group_order_article.rb' - - 'app/models/order_article.rb' - - 'app/models/supplier.rb' - - 'app/models/task.rb' - - 'spec/support/coverage.rb' + - 'app/controllers/suppliers_controller.rb' # Offense count: 6 -# Cop supports --auto-correct. -# Configuration parameters: Strict, AllowedNumbers. -Style/NumericLiterals: - MinDigits: 7 - -# Offense count: 60 -# Cop supports --auto-correct-all. -# Configuration parameters: EnforcedStyle, IgnoredMethods. -# SupportedStyles: predicate, comparison -Style/NumericPredicate: - Enabled: false - -# Offense count: 5 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - 'app/helpers/application_helper.rb' + - 'app/helpers/group_order_articles_helper.rb' - 'app/helpers/orders_helper.rb' - 'app/models/order_article.rb' - 'lib/tasks/foodsoft_setup.rake' # Offense count: 1 -# Cop supports --auto-correct. -Style/OrAssignment: - Exclude: - - 'app/controllers/articles_controller.rb' - -# Offense count: 8 -# Cop supports --auto-correct. -Style/ParallelAssignment: - Exclude: - - 'app/models/article.rb' - - 'app/models/group_order_article.rb' - - 'app/models/supplier.rb' - - 'app/models/user.rb' - - 'spec/models/group_order_article_spec.rb' - - 'spec/support/session_helper.rb' - -# Offense count: 12 -# Cop supports --auto-correct. -# Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. -Style/ParenthesesAroundCondition: - Exclude: - - 'app/controllers/login_controller.rb' - - 'app/helpers/application_helper.rb' - - 'app/helpers/group_orders_helper.rb' - - 'app/models/group_order_article.rb' - - 'plugins/wiki/app/controllers/pages_controller.rb' - -# Offense count: 41 -# Cop supports --auto-correct. -# Configuration parameters: PreferredDelimiters. -Style/PercentLiteralDelimiters: - Enabled: false - -# Offense count: 5 -# Cop supports --auto-correct. -Style/PerlBackrefs: - Exclude: - - 'lib/foodsoft/expansion_variables.rb' - - 'plugins/wiki/app/helpers/pages_helper.rb' - -# Offense count: 2 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: short, verbose Style/PreferredHashMethods: Exclude: - 'app/helpers/admin/configs_helper.rb' - - 'app/helpers/articles_helper.rb' - -# Offense count: 14 -# Cop supports --auto-correct. -Style/Proc: - Exclude: - - 'app/helpers/deliveries_helper.rb' - - 'app/models/user.rb' - - 'config/navigation.rb' - - 'plugins/current_orders/lib/foodsoft_current_orders/engine.rb' - - 'plugins/links/lib/foodsoft_links/engine.rb' - -# Offense count: 6 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowedCompactTypes. -# SupportedStyles: compact, exploded -Style/RaiseArgs: - Exclude: - - 'app/controllers/api/v1/base_controller.rb' - - 'app/controllers/concerns/auth_api.rb' - - 'app/controllers/concerns/foodcoop_scope.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -Style/RandomWithOffset: - Exclude: - - 'db/migrate/007_create_article_prices.rb' - - 'db/migrate/008_create_orders.rb' - - 'db/seeds/seed_helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct-all. -# Configuration parameters: Methods. -Style/RedundantArgument: - Exclude: - - 'app/controllers/articles_controller.rb' - -# Offense count: 8 -# Cop supports --auto-correct. -Style/RedundantBegin: - Exclude: - - 'app/controllers/articles_controller.rb' - - 'app/models/order.rb' - - 'lib/foodsoft_mail_receiver.rb' - - 'lib/tasks/multicoops.rake' - - 'spec/lib/foodsoft_mail_receiver_spec.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantConditional: - Exclude: - - 'app/models/task.rb' # Offense count: 3 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SafeForConstants. Style/RedundantFetchBlock: Exclude: - 'config/puma.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Style/RedundantFileExtensionInRequire: - Exclude: - - 'db/seeds/small.en.seeds.rb' - - 'db/seeds/small.nl.seeds.rb' - -# Offense count: 5 -# Cop supports --auto-correct. +# Offense count: 8 +# This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantInterpolation: Exclude: - 'db/migrate/20130718183101_migrate_user_settings.rb' - - 'lib/order_pdf.rb' + - 'lib/order_txt.rb' + - 'lib/render_csv.rb' + - 'lib/render_pdf.rb' + - 'plugins/current_orders/app/documents/multiple_orders_by_groups.rb' - 'spec/i18n_spec.rb' - 'spec/models/user_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantRegexpCharacterClass: - Exclude: - - 'plugins/wiki/app/helpers/pages_helper.rb' - -# Offense count: 7 -# Cop supports --auto-correct. -Style/RedundantRegexpEscape: - Exclude: - - 'lib/bank_transaction_reference.rb' - - 'lib/foodsoft_mail_receiver.rb' - - 'plugins/documents/app/controllers/documents_controller.rb' - - 'plugins/wiki/app/models/page.rb' - -# Offense count: 15 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleReturnValues. -Style/RedundantReturn: - Exclude: - - 'app/controllers/concerns/auth_api.rb' - - 'app/helpers/application_helper.rb' - - 'app/helpers/deliveries_helper.rb' - - 'app/helpers/group_orders_helper.rb' - - 'app/helpers/orders_helper.rb' - - 'app/models/article.rb' - - 'app/models/bank_transaction.rb' - - 'app/models/periodic_task_group.rb' - - 'app/models/supplier.rb' - - 'lib/bank_transaction_reference.rb' - -# Offense count: 82 -# Cop supports --auto-correct. -Style/RedundantSelf: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantSelfAssignmentBranch: - Exclude: - - 'db/migrate/20130718183101_migrate_user_settings.rb' - -# Offense count: 1 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantSort: Exclude: - 'app/models/article_category.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowInnerSlashes. -# SupportedStyles: slashes, percent_r, mixed -Style/RegexpLiteral: - Exclude: - - 'plugins/wiki/app/models/page.rb' - - 'spec/support/coverage.rb' - -# Offense count: 16 -# Cop supports --auto-correct. -Style/RescueModifier: +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedMethods, AllowedPatterns. +Style/ReturnNilInPredicateMethodDefinition: Exclude: - - 'app/controllers/invites_controller.rb' + - 'app/controllers/group_orders_controller.rb' - 'app/models/article.rb' - - 'app/models/order.rb' - - 'app/models/ordergroup.rb' - - 'config/application.rb' - - 'lib/apple_bar.rb' - - 'lib/date_time_attribute_validate.rb' - - 'lib/foodsoft_date_util.rb' - - 'plugins/messages/app/models/message.rb' -# Offense count: 50 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Enabled: false - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# Offense count: 11 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - 'app/controllers/concerns/auth_api.rb' + - 'app/controllers/concerns/locale.rb' - 'app/controllers/group_order_articles_controller.rb' + - 'app/models/article.rb' - 'app/models/article_category.rb' + - 'app/models/bank_account.rb' - 'app/models/financial_transaction.rb' - - 'app/models/ordergroup.rb' - 'app/models/user.rb' - 'plugins/printer/app/controllers/printer_controller.rb' - 'spec/factories/order.rb' # Offense count: 1 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/SelectByRegexp: Exclude: - 'lib/foodsoft_config.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/SelfAssignment: - Exclude: - - 'app/helpers/application_helper.rb' - -# Offense count: 16 -# Cop supports --auto-correct. -# Configuration parameters: AllowAsExpressionSeparator. -Style/Semicolon: - Exclude: - - 'app/controllers/finance/bank_transactions_controller.rb' - - 'app/controllers/finance/financial_transactions_controller.rb' - - 'app/controllers/finance/invoices_controller.rb' - - 'app/controllers/orders_controller.rb' - - 'app/helpers/group_orders_helper.rb' - - 'db/migrate/20090120184410_road_to_version_three.rb' - - 'spec/api/v1/swagger_spec.rb' - - 'spec/api/v1/user/group_order_articles_spec.rb' - - 'spec/api/v1/user/ordergroup_spec.rb' - - 'spec/models/order_article_spec.rb' - # Offense count: 5 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: Exclude: - 'app/helpers/admin/configs_helper.rb' - 'config/initializers/session_store.rb' - 'lib/order_pdf.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AllowModifier. -Style/SoleNestedConditional: - Exclude: - - 'app/controllers/articles_controller.rb' - - 'app/controllers/concerns/auth.rb' - # Offense count: 9 -# Cop supports --auto-correct. +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: RequireEnglish. -# SupportedStyles: use_perl_names, use_english_names +# SupportedStyles: use_perl_names, use_english_names, use_builtin_english_names Style/SpecialGlobalVars: EnforcedStyle: use_perl_names -# Offense count: 32 -# Cop supports --auto-correct-all. +# Offense count: 35 +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: Enabled: false -# Offense count: 1799 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiterals: - Enabled: false - -# Offense count: 80 -# Cop supports --auto-correct. -# Configuration parameters: MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - EnforcedStyle: brackets - # Offense count: 19 -# Cop supports --auto-correct-all. -# Configuration parameters: AllowMethodsWithArguments, IgnoredMethods. -# IgnoredMethods: respond_to, define_method +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. +# AllowedMethods: define_method, mail, respond_to Style/SymbolProc: Exclude: - 'app/controllers/pickups_controller.rb' @@ -1927,87 +1482,17 @@ Style/SymbolProc: - 'db/migrate/20090731132547_add_stats_to_groups.rb' - 'spec/factories/order.rb' -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowSafeAssignment. -# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex -Style/TernaryParentheses: - Exclude: - - 'app/models/order_article.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArrayLiteral: - Exclude: - - 'lib/articles_csv.rb' - - 'lib/invoices_csv.rb' - - 'lib/ordergroups_csv.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInHashLiteral: - Exclude: - - 'app/controllers/finance/financial_transactions_controller.rb' - - 'config/initializers/exception_notification.rb' - - 'lib/bank_account_information_importer.rb' - -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods. -# AllowedMethods: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym -Style/TrivialAccessors: - Exclude: - - 'app/models/order.rb' - - 'lib/bank_account_connector.rb' - - 'plugins/messages/app/models/message.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -Style/UnlessElse: - Exclude: - - 'app/controllers/home_controller.rb' - - 'app/controllers/orders_controller.rb' - - 'app/helpers/group_order_articles_helper.rb' - - 'plugins/current_orders/app/controllers/current_orders/articles_controller.rb' - - 'plugins/wiki/app/helpers/pages_helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/WhileUntilModifier: - Exclude: - - 'app/models/periodic_task_group.rb' - -# Offense count: 11 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinSize, WordRegex. -# SupportedStyles: percent, brackets -Style/WordArray: - Exclude: - - 'app/documents/order_matrix.rb' - - 'app/helpers/application_helper.rb' - - 'app/models/supplier.rb' - - 'db/migrate/006_create_articles.rb' - - 'lib/tasks/foodsoft_setup.rake' - - 'plugins/current_orders/app/controllers/current_orders/group_orders_controller.rb' - - 'plugins/wiki/app/controllers/pages_controller.rb' - - 'plugins/wiki/app/helpers/pages_helper.rb' - - 'spec/support/faker.rb' - # Offense count: 3 -# Cop supports --auto-correct-all. +# This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - 'app/models/group_order_article.rb' - 'plugins/current_orders/app/documents/multiple_orders_by_articles.rb' - 'plugins/current_orders/app/documents/multiple_orders_by_groups.rb' -# Offense count: 448 -# Cop supports --auto-correct. -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# Offense count: 805 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # URISchemes: http, https Layout/LineLength: - Max: 420 + Max: 311 diff --git a/Dockerfile-dev b/Dockerfile-dev index 255a8ac4..ca7865a5 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,7 +1,7 @@ FROM ruby:2.6 # Install dependencies -RUN deps='libmagic-dev chromium' && \ +RUN deps='libmagic-dev chromium nodejs' && \ apt-get update && \ apt-get install --no-install-recommends -y $deps && \ rm -Rf /var/lib/apt/lists/* /var/cache/apt/* diff --git a/Gemfile b/Gemfile index 1a56b334..0bf0d832 100644 --- a/Gemfile +++ b/Gemfile @@ -75,6 +75,7 @@ group :development do gem 'mailcatcher' gem 'web-console' gem 'listen' + gem 'byebug' # Better error output gem 'better_errors' diff --git a/Gemfile.lock b/Gemfile.lock index 90d7272d..4c2d9a37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,6 +144,7 @@ GEM bullet (7.0.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) + byebug (11.1.3) capybara (3.36.0) addressable matrix @@ -577,6 +578,7 @@ DEPENDENCIES bootsnap bootstrap-datepicker-rails bullet + byebug capybara connection_pool daemons diff --git a/HACKATHON_TODOS.md b/HACKATHON_TODOS.md new file mode 100644 index 00000000..87af52de --- /dev/null +++ b/HACKATHON_TODOS.md @@ -0,0 +1,12 @@ +# TODO-no-upstream - whole file +- Replace all remaining occurrences of `.unit_quantity` +- Receive orders page: + * Determine +/- button functionality for very small or non-existant pack sizes + * BE validation of order amount + * Investigate total_balance hidden field meaning (old issue) + * Formatting: Alignment breaks with numbers that have more than one digit (old issue; but renewed importance due to floats) + * Article (version) Field translations of new / moved fields + +# POST-merge: + +* Close https://github.com/foodcoops/foodsoft/issues/396 diff --git a/Procfile b/Procfile index ee18cb40..a90d4ede 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,5 @@ -web: bundle exec rails server --binding=0.0.0.0 --port=$PORT +# TODO-no-upstream: +web: bundle exec rails server --binding=0.0.0.0 --port=$PORT; tail -f log/development.log worker: QUEUE=* bundle exec rake resque:work mail: bundle exec rake foodsoft:reply_email_smtp_server cron: supercronic crontab diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 835180b2..e59b186f --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,7 @@ #!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, -require File.expand_path('../config/application', __FILE__) +require File.expand_path('config/application', __dir__) require 'rake' require 'rspec-rerun/tasks' if defined?(RSpec) # http://stackoverflow.com/a/16853615/2866660 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index ebe63685..c07a73f9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -15,7 +15,13 @@ //= require i18n //= require i18n/translations //= require_self -//= require ordering +//= require big +//= require units-converter +//= require migrate-units-form +//= require article-form +//= require unit-conversion-field +//= require group-order-form +//= require receive-order-form //= require stupidtable //= require touchclick //= require delta_input @@ -23,6 +29,18 @@ $.fn.select2.defaults.set('theme', 'bootstrap'); +// on first focus (bubbles up to document), open the menu +$(document).on('focus', '.select2-selection.select2-selection--single', function () { + $(this).closest(".select2-container").siblings('select:enabled').select2('open'); +}); + +// steal focus during close - only capture once and stop propogation +$('select.select2').on('select2:closing', function (e) { + $(e.target).data("select2").$selection.one('focus focusin', function (e) { + e.stopPropagation(); + }); +}); + // Load following statements, when DOM is ready $(function() { @@ -94,6 +112,10 @@ $(function() { // trigger timeout to submit form when value was changed clearTimeout(input.data('submit-timeout-id')); input.data('submit-timeout-id', setTimeout(function() { + if (input.data('multiply-before-submit')) { + input.parents('form').find(`input[type="hidden"][name="${input.attr('name')}"]`).remove(); + input.parents('form').append(``); + } if (input.val() != input.data('old-value')) input.parents('form').submit(); input.removeData('submit-timeout-id'); input.removeData('old-value'); @@ -121,6 +143,10 @@ $(function() { // Handle ajax errors // render json: {error: "can't except this!"}, status: :unprocessable_entity $(document).ajaxError(function(ev, xhr, settings, exception) { + if (xhr.statusText === 'abort') { + return; + } + try { msg = xhr.responseJSON.error; } catch(err) { diff --git a/app/assets/javascripts/article-form.js b/app/assets/javascripts/article-form.js new file mode 100644 index 00000000..88ff935f --- /dev/null +++ b/app/assets/javascripts/article-form.js @@ -0,0 +1,592 @@ +class ArticleForm { + constructor(articleUnitRatioTemplate$, articleForm$, units, priceMarkup, multiForm$, unitFieldsIdPrefix, unitFieldsNamePrefix) { + try { + this.units = units; + this.priceMarkup = priceMarkup; + this.unitFieldsIdPrefix = unitFieldsIdPrefix === undefined ? 'article_version' : unitFieldsIdPrefix; + this.unitFieldsNamePrefix = unitFieldsNamePrefix === undefined ? this.unitFieldsIdPrefix : unitFieldsNamePrefix; + this.articleUnitRatioTemplate$ = articleUnitRatioTemplate$; + this.articleForm$ = articleForm$; + this.unitConversionPopoverTemplate$ = $('#unit_conversion_popover_content_template'); + this.unit$ = $(`#${this.unitFieldsIdPrefix}_unit`, this.articleForm$); + this.customUnitWarning$ = $('.icon-warning-sign', this.articleForm$); + this.supplierUnitSelect$ = $(`#${this.unitFieldsIdPrefix}_supplier_order_unit`, this.articleForm$); + this.unitRatiosTable$ = $('#fc_base_price', this.articleForm$); + this.minimumOrderQuantity$ = $(`#${this.unitFieldsIdPrefix}_minimum_order_quantity`, this.articleForm$); + this.billingUnit$ = $(`#${this.unitFieldsIdPrefix}_billing_unit`, this.articleForm$); + this.groupOrderGranularity$ = $(`#${this.unitFieldsIdPrefix}_group_order_granularity`, this.articleForm$); + this.groupOrderUnit$ = $(`#${this.unitFieldsIdPrefix}_group_order_unit`, this.articleForm$); + this.price$ = $(`#${this.unitFieldsIdPrefix}_price`, this.articleForm$); + this.priceUnit$ = $(`#${this.unitFieldsIdPrefix}_price_unit`, this.articleForm$); + this.tax$ = $(`#${this.unitFieldsIdPrefix}_tax`, this.articleForm$); + this.deposit$ = $(`#${this.unitFieldsIdPrefix}_deposit`, this.articleForm$); + this.fcPrice$ = $(`#article_fc_price`, this.articleForm$); + this.unitsToOrder$ = $('#order_article_units_to_order', this.articleForm$); + this.unitsReceived$ = $('#order_article_units_received', this.articleForm$); + this.toggleExtraUnitsButton$ = $('.toggle-extra-units', this.articleForm$); + this.extraUnits$ = $('.extra-unit-fields', this.articleForm$); + this.multiForm$ = multiForm$; + const selectContainer$ = this.articleForm$.parents('#modalContainer'); + this.select2Config = { + dropdownParent: selectContainer$.length === 0 ? undefined : selectContainer$ + }; + + this.loadAvailableUnits(); + this.initializeRegularFormFields(); + + this.initializeRatioRows(); + this.bindAddRatioButton(); + + this.setFieldVisibility(); + + this.loadRatios(); + this.prepareRatioDataForSequentialRepresentation(); + this.convertPriceToPriceUnit(); + this.initializePriceDisplay(); + this.initializeOrderedAndReceivedUnits(); + this.convertOrderedAndReceivedUnits(this.supplierUnitSelect$.val(), this.billingUnit$.val()); + this.initializeFormSubmitListener(); + this.initializeToggleExtraUnitsButton(); + } catch (e) { + console.log('Could not initialize article form', e, 'articleUnitRatioTemplate$', articleUnitRatioTemplate$, 'articleForm$', articleForm$, 'units', units, 'priceMarkup', priceMarkup, 'multiForm$', multiForm$, 'unitFieldsIdPrefix', unitFieldsIdPrefix, 'unitFieldsNamePrefix', unitFieldsNamePrefix); + } + } + + initializePriceDisplay() { + mergeJQueryObjects([this.price$, this.priceUnit$]).on('change keyup', () => { + const price = parseFloat(this.price$.val()); + const tax = parseFloat(this.tax$.val()); + const deposit = parseFloat(this.deposit$.val()); + const grossPrice = (price + deposit) * (tax / 100 + 1); + const fcPrice = grossPrice * (this.priceMarkup / 100 + 1); + const priceUnitLabel = this.getUnitLabel(this.priceUnit$.val()); + this.fcPrice$.find('.price_value').text(isNaN(fcPrice) ? '?' : I18n.l('currency', fcPrice)); + this.fcPrice$.find('.price_per_text').toggle(priceUnitLabel.trim() !== ''); + this.fcPrice$.find('.price_unit').text(priceUnitLabel); + }); + + this.price$.trigger('change'); + } + + getUnitLabel(unitKey) { + if (unitKey === '') { + return this.unit$.val(); + } + const unit = this.availableUnits.find((availableUnit) => availableUnit.key === unitKey); + if (unit === undefined) { + return '?'; + } + return unit.symbol != null ? unit.symbol : unit.label; + } + + initializeFormSubmitListener() { + (this.multiForm$ === undefined ? this.articleForm$ : this.multiForm$).submit((e) => { + try { + this.undoSequentialRatioDataRepresentation(); + this.loadRatios(); + this.undoPriceConversion(); + this.undoOrderAndReceivedUnitsConversion(); + } catch(err) { + e.preventDefault(); + throw err; + } + }); + } + + initializeToggleExtraUnitsButton() { + if (this.toggleExtraUnitsButton$.length > 0) { + this.setExtraUnitsButtonStatus(); + + this.toggleExtraUnitsButton$.on('click', (e) => { + this.toggleExtraUnits(); + }); + + this.supplierUnitSelect$.on('change', () => this.setExtraUnitsButtonStatus()); + } + } + + setExtraUnitsButtonStatus() { + if (this.hasDeviatingExtraUnits()) { + this.toggleExtraUnitsButton$.removeClass('default-values'); + } else { + this.toggleExtraUnitsButton$.addClass('default-values'); + } + } + + hasDeviatingExtraUnits() { + if ($(`input[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[quantity]"]`).length > 0) { + return true; + } + + const supplierOrderUnit = this.supplierUnitSelect$.val(); + if (supplierOrderUnit !== this.groupOrderUnit$.val() || supplierOrderUnit !== this.billingUnit$.val()) { + return true; + } + + if (this.minimumOrderQuantity$.val().trim() !== '' || parseFloat(this.groupOrderGranularity$.val().trim()) !== 1) { + return true; + } + + return false; + } + + toggleExtraUnits() { + this.setExtraUnitsButtonStatus(); + $(document).off('mousedown.extra-units'); + this.extraUnits$.toggleClass('show'); + this.toggleExtraUnitsButton$.toggleClass('show'); + + if (this.extraUnits$.hasClass('show')) { + $(document).on('mousedown.extra-units', (e) => { + if ($(e.target).parents(this.extraUnits$.selector).length !== 0 || e.target === this.extraUnits$[0] || e.target === this.toggleExtraUnitsButton$[0]) { + return; + } + + this.toggleExtraUnits(); + }); + } + } + + getUnitsConverter() { + return new UnitsConverter(this.units, this.ratios, this.supplierUnitSelect$.val()); + } + + getUnitRatio(quantity, inputUnit, outputUnit) { + const converter = this.getUnitsConverter(); + return converter.getUnitRatio(quantity, inputUnit, outputUnit); + } + + undoPriceConversion() { + const relativePrice = this.price$.val(); + const priceUnit = this.priceUnit$.val(); + if (priceUnit === undefined) { + // TODO-article-version: StockArticles don't have a price unit? + return; + } + const ratio = this.getUnitRatio(1, priceUnit, this.supplierUnitSelect$.val()); + const supplierUnitPrice = relativePrice / ratio; + const hiddenPriceField$ = $(``); + this.articleForm$.append(hiddenPriceField$); + } + + undoOrderAndReceivedUnitsConversion() { + this.convertOrderedAndReceivedUnits(this.billingUnit$.val(), this.supplierUnitSelect$.val()); + } + + loadAvailableUnits() { + this.availableUnits = Object.entries(this.units) + .filter(([, unit]) => unit.visible) + .map(([code, unit]) => ({ key: code, label: unit.name, baseUnit: unit.baseUnit, symbol: unit.symbol, aliases: unit.aliases ? unit.aliases : [] })); + + $(`#${this.unitFieldsIdPrefix}_supplier_order_unit`, this.articleForm$).select2(this.select2Config); + } + + initializeRegularFormFields() { + this.unit$.change(() => { + this.setMinimumOrderUnitDisplay(); + this.updateAvailableBillingAndGroupOrderUnits(); + this.updateUnitMultiplierLabels(); + this.updateCustomUnitWarning(); + }); + this.updateCustomUnitWarning(); + this.unit$.keyup(() => this.unit$.trigger('change')); + + + this.supplierUnitSelect$.change(() => { + this.onSupplierUnitChanged(); + this.updateCustomUnitWarning(); + }); + this.onSupplierUnitChanged(); + } + + updateCustomUnitWarning() { + const supplierUnitValueChosen = this.supplierUnitSelect$.val() !== undefined && this.supplierUnitSelect$.val().trim() !== ''; + if (supplierUnitValueChosen) { + this.customUnitWarning$.hide(); + return; + } + + const unitVal = this.unit$.val().trim().toLowerCase(); + if (unitVal !== '' && (unitVal.match(/[0-9]/) || this.availableUnits.some((unit) => (unit.symbol != null && unit.symbol.toLowerCase() === unitVal) || unit.label.toLowerCase() === unitVal || unit.aliases.some((alias) => alias.toLowerCase() === unitVal)))) { + this.customUnitWarning$.show(); + } else { + this.customUnitWarning$.hide(); + } + } + + onSupplierUnitChanged() { + const valueChosen = this.supplierUnitSelect$.val() !== undefined && this.supplierUnitSelect$.val().trim() !== ''; + this.unit$.prop('disabled', valueChosen); + this.unit$.toggle(!valueChosen); + this.filterAvailableRatioUnits(); + this.setMinimumOrderUnitDisplay(); + this.updateAvailableBillingAndGroupOrderUnits(); + this.updateUnitMultiplierLabels(); + } + + setMinimumOrderUnitDisplay() { + const chosenOptionLabel = this.supplierUnitSelect$.val() !== '' + ? $(`option[value="${this.supplierUnitSelect$.val()}"]`, this.supplierUnitSelect$).text() + : undefined; + const unitVal = $(`#${this.unitFieldsIdPrefix}_unit`).val(); + this.minimumOrderQuantity$ + .parents('.input-append') + .find('.add-on') + .text(chosenOptionLabel !== undefined ? chosenOptionLabel : unitVal); + + const converter = this.getUnitsConverter(); + if (converter.isUnitSiConversible(this.supplierUnitSelect$.val())) { + this.minimumOrderQuantity$.removeAttr('step'); + } else { + this.minimumOrderQuantity$.attr('step', 1); + } + } + + bindAddRatioButton() { + $('*[data-add-ratio]', this.articleForm$).on('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + + this.onAddRatioClicked(); + }); + } + + onAddRatioClicked() { + const newRow$ = this.articleUnitRatioTemplate$.clone(); + $('tbody', this.unitRatiosTable$).append(newRow$); + + const index = $(`input[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[sort]"]`, this.articleForm$).length + + $(`input[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[_destroy]"]`, this.articleForm$).length; + + const sortField$ = $('[name$="[sort]"]', newRow$); + sortField$.val(index); + + const ratioAttributeFields$ = $(`[id^="${this.unitFieldsIdPrefix}_article_unit_ratios_attributes_0_"]`, newRow$); + ratioAttributeFields$.each((_, field) => { + $(field).attr('name', $(field).attr('name').replace('[0]', `[${index}]`)); + $(field).attr('id', $(field).attr('id').replace(`${this.unitFieldsIdPrefix}_article_unit_ratios_attributes_0_`, `${this.unitFieldsIdPrefix}_article_unit_ratios_attributes_${index}_`)); + }); + + this.setFieldVisibility(); + + this.initializeRatioRows(); + } + + initializeRatioRows() { + $('tr', this.unitRatiosTable$).each((_, row) => { + this.initializeRatioRow($(row)); + }); + + this.updateUnitMultiplierLabels(); + this.filterAvailableRatioUnits(); + } + + initializeRatioRow(row$) { + $('*[data-remove-ratio]', row$) + .off('click.article_form_ratio_row') + .on('click.article_form_ratio_row', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.removeRatioRow($(e.target).closest('tr')); + }); + + const select$ = $('select[name$="[unit]"]', row$); + select$.change(() => { + this.filterAvailableRatioUnits(row$) + this.updateUnitMultiplierLabels(); + }); + select$.select2(this.select2Config); + } + + updateUnitMultiplierLabels() { + $('tr', this.unitRatiosTable$).each((_, row) => { + const row$ = $(row); + const aboveUnit = this.findAboveUnit(row$); + $('.unit_multiplier', row$).text(aboveUnit); + }); + } + + removeRatioRow(row$) { + const index = row$.index() + 1; + const id = $(`[name="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes][${index}][id]"]`, this.articleForm$).val(); + row$.remove(); + + $(this.unitRatiosTable$).after($(``)); + $(this.unitRatiosTable$).after($(``)); + this.filterAvailableRatioUnits(); + this.updateUnitMultiplierLabels(); + this.setFieldVisibility(); + } + + filterAvailableRatioUnits() { + const isUnitOrBaseUnitSelected = (unit, select$) => { + const code = select$.val(); + const selectedUnit = this.units[code]; + return unit.key !== code && (!unit.baseUnit || !selectedUnit || !selectedUnit.baseUnit || unit.baseUnit !== selectedUnit.baseUnit); + }; + + let remainingAvailableUnits = this.availableUnits.filter(unit => isUnitOrBaseUnitSelected(unit, this.supplierUnitSelect$)); + + $('tr select[name$="[unit]"]', this.unitRatiosTable$).each((_, unitSelect) => { + $('option[value!=""]' + remainingAvailableUnits.map(unit => `[value!="${unit.key}"]`).join(''), unitSelect).remove(); + const missingUnits = remainingAvailableUnits.filter(unit => $(`option[value="${unit.key}"]`, unitSelect).length === 0); + for (const missingUnit of missingUnits) { + $(unitSelect).append($(``)); + } + remainingAvailableUnits = remainingAvailableUnits.filter(unit => isUnitOrBaseUnitSelected(unit, $(unitSelect))); + }); + + this.updateAvailableBillingAndGroupOrderUnits(); + } + + findAboveUnit(row$) { + const previousRow$ = row$.prev(); + if (previousRow$.length > 0) { + const unitKey = previousRow$.find('select[name$="[unit]"]').val(); + const unit = this.availableUnits.find(availableUnit => availableUnit.key === unitKey); + if (!unit) { + return '?'; + } + return unit.label; + } else { + const unitKey = this.supplierUnitSelect$.val(); + if (unitKey !== '') { + const unit = this.availableUnits.find(availableUnit => availableUnit.key === unitKey); + if (!unit) { + return '?'; + } + return unit.label; + } else { + const unitVal = this.unit$.val(); + return unitVal ? unitVal : '?'; + } + } + } + + updateAvailableBillingAndGroupOrderUnits() { + const unitsSelectedAbove = []; + if (this.supplierUnitSelect$.val() != '') { + const chosenOption$ = $(`option[value="${this.supplierUnitSelect$.val()}"]`, this.supplierUnitSelect$); + unitsSelectedAbove.push({ key: chosenOption$.val(), label: chosenOption$.text() }); + } else { + const unitVal = this.unit$.val(); + unitsSelectedAbove.push({ key: '', label: unitVal ? unitVal : '?' }); + } + + const selectedRatioUnits = $('tr select[name$="[unit]"]', this.unitRatiosTable$).map((_, ratioSelect) => ({ + key: $(ratioSelect).val(), + label: $(`option[value="${$(ratioSelect).val()}"]`, ratioSelect).text() + })) + .get() + .filter(option => option.key !== ''); + + unitsSelectedAbove.push(...selectedRatioUnits); + + const availableUnits = []; + for (const unitSelectedAbove of unitsSelectedAbove) { + availableUnits.push(unitSelectedAbove, ...this.availableUnits.filter(availableUnit => { + if (availableUnit.key === unitSelectedAbove.key) { + return false; + } + + const otherUnit = this.availableUnits.find(unit => unit.key === unitSelectedAbove.key); + return otherUnit !== undefined && otherUnit.baseUnit !== null && availableUnit.baseUnit === otherUnit.baseUnit; + })); + } + + this.updateUnitsInSelect(availableUnits, this.billingUnit$); + this.billingUnit$.parents('.fold-line').css('display', availableUnits.length > 1 ? 'block' : 'none'); + this.updateUnitsInSelect(availableUnits, this.groupOrderUnit$); + this.updateUnitsInSelect(availableUnits, this.priceUnit$); + } + + updateUnitsInSelect(units, unitSelect$) { + const valueBeforeUpdate = unitSelect$.val(); + + unitSelect$.empty(); + for (const unit of units) { + unitSelect$.append($(``)); + } + + const initialValue = unitSelect$.attr('data-initial-value'); + if (initialValue) { + unitSelect$.val(initialValue); + unitSelect$.removeAttr('data-initial-value'); + } else { + if (unitSelect$.find(`option[value="${valueBeforeUpdate}"]`).length > 0) { + unitSelect$.val(valueBeforeUpdate); + } else { + unitSelect$.val(unitSelect$.find('option:first').val()); + } + } + + unitSelect$.trigger('change'); + + unitSelect$.parents('.control-group').find('.immutable_unit_label').remove(); + if (units.length === 1) { + unitSelect$.hide(); + unitSelect$.parents('.control-group').append($(`
${units[0].label}
`)) + } else { + unitSelect$.show(); + } + } + + setFieldVisibility() { + const firstUnitRatioQuantity$ = $('tr input[name$="[quantity]"]:first', this.unitRatiosTable$); + const firstUnitRatioUnit$ = $('tr select[name$="[unit]"]:first', this.unitRatiosTable$); + + const supplierOrderUnitSet = !!this.unit$.val() || !!this.supplierUnitSelect$.val(); + const unitRatiosVisible = supplierOrderUnitSet || this.unitRatiosTable$.find('tbody tr').length > 0; + this.unitRatiosTable$.parents('.fold-line').toggle(unitRatiosVisible); + + if (!unitRatiosVisible) { + $('tbody tr', this.unitRatiosTable$).remove(); + } + + mergeJQueryObjects([ + this.unit$, + this.supplierUnitSelect$, + firstUnitRatioQuantity$, + firstUnitRatioUnit$ + ]).off('change.article_form_visibility') + .on('change.article_form_visibility', () => + this.setFieldVisibility() + ); + + firstUnitRatioQuantity$ + .off('keyup.article_form_visibility') + .on('keyup.article_form_visibility', () => firstUnitRatioQuantity$.trigger('change')); + } + + prepareRatioDataForSequentialRepresentation() { + const numberOfRatios = $(`input[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[quantity]"]`).length; + + for (let i = numberOfRatios; i > 1; i--) { + const currentField$ = $(`input[name="${this.ratioQuantityFieldNameByIndex(i)}"]`, this.articleForm$); + const currentValue = currentField$.val(); + const previousValue = $(`input[name="${this.ratioQuantityFieldNameByIndex(i - 1)}"]:last`, this.articleForm$).val(); + currentField$.val(round(currentValue / previousValue)); + } + } + + convertPriceToPriceUnit() { + const supplierUnitPrice = this.price$.val(); + const priceUnit = this.priceUnit$.val(); + if (priceUnit === undefined) { + // TODO-article-version: StockArticles don't have a price unit? + return; + } + const ratio = this.getUnitRatio(1, priceUnit, this.supplierUnitSelect$.val()); + const relativePrice = round(supplierUnitPrice * ratio); + this.price$.val(relativePrice); + } + + initializeOrderedAndReceivedUnits() { + this.billingUnit$.change(() => { + this.updateOrderedAndReceivedUnits(); + this.initializeOrderedAndReceivedUnitsConverters(); + }); + this.billingUnit$.trigger('change'); + } + + updateOrderedAndReceivedUnits() { + const billingUnitKey = this.billingUnit$.val(); + const billingUnitLabel = this.getUnitLabel(billingUnitKey); + const inputs$ = mergeJQueryObjects([this.unitsToOrder$, this.unitsReceived$]); + inputs$.parent().find('.unit_label').remove(); + if (billingUnitLabel.trim() !== '') { + inputs$.after($(`${this.getUnitsConverter().isUnitSiConversible(billingUnitKey) ? '' : 'x '}${billingUnitLabel}`)); + } + if (this.previousBillingUnit !== undefined) { + this.convertOrderedAndReceivedUnits(this.previousBillingUnit, billingUnitKey); + } + this.previousBillingUnit = billingUnitKey; + } + + convertOrderedAndReceivedUnits(fromUnit, toUnit) { + const inputs$ = mergeJQueryObjects([this.unitsToOrder$, this.unitsReceived$]); + inputs$.each((_, input) => { + const input$ = $(input); + const val = input$.val(); + + if (val !== '') { + try { + const convertedValue = this.getUnitRatio(val, fromUnit, toUnit); + input$.val(round(convertedValue)); + } catch (e) { + // In some cases it's impossible to perform this conversion - just leave the original value + } + } + }); + } + + initializeOrderedAndReceivedUnitsConverters() { + this.unitsToOrder$.unitConversionField('destroy'); + this.unitsReceived$.unitConversionField('destroy'); + + const opts = { + units: this.units, + popoverTemplate$: this.unitConversionPopoverTemplate$, + ratios: this.ratios, + supplierOrderUnit: this.supplierUnitSelect$.val(), + customUnit: this.unit$.val(), + defaultUnit: this.billingUnit$.val() + }; + this.unitsToOrder$.unitConversionField(opts); + this.unitsReceived$.unitConversionField(opts); + } + + loadRatios() { + this.ratios = []; + this.unitRatiosTable$.find('tbody tr').each((_, element) => { + const tr$ = $(element); + const unit = tr$.find(`select[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[unit]"]`).val(); + const quantity = tr$.find(`input[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[quantity]"]:last`).val(); + this.ratios.push({ unit, quantity }); + }); + } + + undoSequentialRatioDataRepresentation() { + let previousValue; + $(`input[name^="${this.unitFieldsNamePrefix}[article_unit_ratios_attributes]"][name$="[quantity]"]`).each((_, field) => { + let currentField$ = $(field); + let quantity = currentField$.val(); + + if (previousValue !== undefined) { + const td$ = currentField$.closest('td'); + const name = currentField$.attr('name'); + const ratioNameRegex = new RegExp(`${escapeForRegex(this.unitFieldsNamePrefix)}\\[article_unit_ratios_attributes\\]\\[([0-9]+)\\]`); + const index = name.match(ratioNameRegex)[1]; + quantity = quantity * previousValue; + currentField$ = $(``); + td$.append(currentField$); + } + + previousValue = quantity; + }); + } + + ratioQuantityFieldNameByIndex(i) { + return `${this.unitFieldsNamePrefix}[article_unit_ratios_attributes][${i}][quantity]`; + } +} + + +// TODO: Move those functions to some global js utils file: +function mergeJQueryObjects(array_of_jquery_objects) { + return $($.map(array_of_jquery_objects, function (el) { + return el.get(); + })); +} + +function round(num, precision) { + if (precision === undefined) { + precision = 3; + } + const factor = Math.pow(10, precision); + return Math.round((num + Number.EPSILON) * factor) / factor; +} + +function escapeForRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} diff --git a/app/assets/javascripts/big.js b/app/assets/javascripts/big.js new file mode 100644 index 00000000..2f2be034 --- /dev/null +++ b/app/assets/javascripts/big.js @@ -0,0 +1,1043 @@ +/* + * big.js v6.2.1 + * A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic. + * Copyright (c) 2022 Michael Mclaughlin + * https://github.com/MikeMcl/big.js/LICENCE.md + */ +(function (GLOBAL) { + 'use strict'; + var Big, + + +/************************************** EDITABLE DEFAULTS *****************************************/ + + + // The default values below must be integers within the stated ranges. + + /* + * The maximum number of decimal places (DP) of the results of operations involving division: + * div and sqrt, and pow with negative exponents. + */ + DP = 20, // 0 to MAX_DP + + /* + * The rounding mode (RM) used when rounding to the above decimal places. + * + * 0 Towards zero (i.e. truncate, no rounding). (ROUND_DOWN) + * 1 To nearest neighbour. If equidistant, round up. (ROUND_HALF_UP) + * 2 To nearest neighbour. If equidistant, to even. (ROUND_HALF_EVEN) + * 3 Away from zero. (ROUND_UP) + */ + RM = 1, // 0, 1, 2 or 3 + + // The maximum value of DP and Big.DP. + MAX_DP = 1E6, // 0 to 1000000 + + // The maximum magnitude of the exponent argument to the pow method. + MAX_POWER = 1E6, // 1 to 1000000 + + /* + * The negative exponent (NE) at and beneath which toString returns exponential notation. + * (JavaScript numbers: -7) + * -1000000 is the minimum recommended exponent value of a Big. + */ + NE = -7, // 0 to -1000000 + + /* + * The positive exponent (PE) at and above which toString returns exponential notation. + * (JavaScript numbers: 21) + * 1000000 is the maximum recommended exponent value of a Big, but this limit is not enforced. + */ + PE = 21, // 0 to 1000000 + + /* + * When true, an error will be thrown if a primitive number is passed to the Big constructor, + * or if valueOf is called, or if toNumber is called on a Big which cannot be converted to a + * primitive number without a loss of precision. + */ + STRICT = false, // true or false + + +/**************************************************************************************************/ + + + // Error messages. + NAME = '[big.js] ', + INVALID = NAME + 'Invalid ', + INVALID_DP = INVALID + 'decimal places', + INVALID_RM = INVALID + 'rounding mode', + DIV_BY_ZERO = NAME + 'Division by zero', + + // The shared prototype object. + P = {}, + UNDEFINED = void 0, + NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; + + + /* + * Create and return a Big constructor. + */ + function _Big_() { + + /* + * The Big constructor and exported function. + * Create and return a new instance of a Big number object. + * + * n {number|string|Big} A numeric value. + */ + function Big(n) { + var x = this; + + // Enable constructor usage without new. + if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n); + + // Duplicate. + if (n instanceof Big) { + x.s = n.s; + x.e = n.e; + x.c = n.c.slice(); + } else { + if (typeof n !== 'string') { + if (Big.strict === true && typeof n !== 'bigint') { + throw TypeError(INVALID + 'value'); + } + + // Minus zero? + n = n === 0 && 1 / n < 0 ? '-0' : String(n); + } + + parse(x, n); + } + + // Retain a reference to this Big constructor. + // Shadow Big.prototype.constructor which points to Object. + x.constructor = Big; + } + + Big.prototype = P; + Big.DP = DP; + Big.RM = RM; + Big.NE = NE; + Big.PE = PE; + Big.strict = STRICT; + Big.roundDown = 0; + Big.roundHalfUp = 1; + Big.roundHalfEven = 2; + Big.roundUp = 3; + + return Big; + } + + + /* + * Parse the number or string value passed to a Big constructor. + * + * x {Big} A Big number instance. + * n {number|string} A numeric value. + */ + function parse(x, n) { + var e, i, nl; + + if (!NUMERIC.test(n)) { + throw Error(INVALID + 'number'); + } + + // Determine sign. + x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1; + + // Decimal point? + if ((e = n.indexOf('.')) > -1) n = n.replace('.', ''); + + // Exponential form? + if ((i = n.search(/e/i)) > 0) { + + // Determine exponent. + if (e < 0) e = i; + e += +n.slice(i + 1); + n = n.substring(0, i); + } else if (e < 0) { + + // Integer. + e = n.length; + } + + nl = n.length; + + // Determine leading zeros. + for (i = 0; i < nl && n.charAt(i) == '0';) ++i; + + if (i == nl) { + + // Zero. + x.c = [x.e = 0]; + } else { + + // Determine trailing zeros. + for (; nl > 0 && n.charAt(--nl) == '0';); + x.e = e - i - 1; + x.c = []; + + // Convert string to array of digits without leading/trailing zeros. + for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++); + } + + return x; + } + + + /* + * Round Big x to a maximum of sd significant digits using rounding mode rm. + * + * x {Big} The Big to round. + * sd {number} Significant digits: integer, 0 to MAX_DP inclusive. + * rm {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up). + * [more] {boolean} Whether the result of division was truncated. + */ + function round(x, sd, rm, more) { + var xc = x.c; + + if (rm === UNDEFINED) rm = x.constructor.RM; + if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) { + throw Error(INVALID_RM); + } + + if (sd < 1) { + more = + rm === 3 && (more || !!xc[0]) || sd === 0 && ( + rm === 1 && xc[0] >= 5 || + rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED)) + ); + + xc.length = 1; + + if (more) { + + // 1, 0.1, 0.01, 0.001, 0.0001 etc. + x.e = x.e - sd + 1; + xc[0] = 1; + } else { + + // Zero. + xc[0] = x.e = 0; + } + } else if (sd < xc.length) { + + // xc[sd] is the digit after the digit that may be rounded up. + more = + rm === 1 && xc[sd] >= 5 || + rm === 2 && (xc[sd] > 5 || xc[sd] === 5 && + (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) || + rm === 3 && (more || !!xc[0]); + + // Remove any digits after the required precision. + xc.length = sd; + + // Round up? + if (more) { + + // Rounding up may mean the previous digit has to be rounded up. + for (; ++xc[--sd] > 9;) { + xc[sd] = 0; + if (sd === 0) { + ++x.e; + xc.unshift(1); + break; + } + } + } + + // Remove trailing zeros. + for (sd = xc.length; !xc[--sd];) xc.pop(); + } + + return x; + } + + + /* + * Return a string representing the value of Big x in normal or exponential notation. + * Handles P.toExponential, P.toFixed, P.toJSON, P.toPrecision, P.toString and P.valueOf. + */ + function stringify(x, doExponential, isNonzero) { + var e = x.e, + s = x.c.join(''), + n = s.length; + + // Exponential notation? + if (doExponential) { + s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e; + + // Normal notation. + } else if (e < 0) { + for (; ++e;) s = '0' + s; + s = '0.' + s; + } else if (e > 0) { + if (++e > n) { + for (e -= n; e--;) s += '0'; + } else if (e < n) { + s = s.slice(0, e) + '.' + s.slice(e); + } + } else if (n > 1) { + s = s.charAt(0) + '.' + s.slice(1); + } + + return x.s < 0 && isNonzero ? '-' + s : s; + } + + + // Prototype/instance methods + + + /* + * Return a new Big whose value is the absolute value of this Big. + */ + P.abs = function () { + var x = new this.constructor(this); + x.s = 1; + return x; + }; + + + /* + * Return 1 if the value of this Big is greater than the value of Big y, + * -1 if the value of this Big is less than the value of Big y, or + * 0 if they have the same value. + */ + P.cmp = function (y) { + var isneg, + x = this, + xc = x.c, + yc = (y = new x.constructor(y)).c, + i = x.s, + j = y.s, + k = x.e, + l = y.e; + + // Either zero? + if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i; + + // Signs differ? + if (i != j) return i; + + isneg = i < 0; + + // Compare exponents. + if (k != l) return k > l ^ isneg ? 1 : -1; + + j = (k = xc.length) < (l = yc.length) ? k : l; + + // Compare digit by digit. + for (i = -1; ++i < j;) { + if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1; + } + + // Compare lengths. + return k == l ? 0 : k > l ^ isneg ? 1 : -1; + }; + + + /* + * Return a new Big whose value is the value of this Big divided by the value of Big y, rounded, + * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM. + */ + P.div = function (y) { + var x = this, + Big = x.constructor, + a = x.c, // dividend + b = (y = new Big(y)).c, // divisor + k = x.s == y.s ? 1 : -1, + dp = Big.DP; + + if (dp !== ~~dp || dp < 0 || dp > MAX_DP) { + throw Error(INVALID_DP); + } + + // Divisor is zero? + if (!b[0]) { + throw Error(DIV_BY_ZERO); + } + + // Dividend is 0? Return +-0. + if (!a[0]) { + y.s = k; + y.c = [y.e = 0]; + return y; + } + + var bl, bt, n, cmp, ri, + bz = b.slice(), + ai = bl = b.length, + al = a.length, + r = a.slice(0, bl), // remainder + rl = r.length, + q = y, // quotient + qc = q.c = [], + qi = 0, + p = dp + (q.e = x.e - y.e) + 1; // precision of the result + + q.s = k; + k = p < 0 ? 0 : p; + + // Create version of divisor with leading zero. + bz.unshift(0); + + // Add zeros to make remainder as long as divisor. + for (; rl++ < bl;) r.push(0); + + do { + + // n is how many times the divisor goes into current remainder. + for (n = 0; n < 10; n++) { + + // Compare divisor and remainder. + if (bl != (rl = r.length)) { + cmp = bl > rl ? 1 : -1; + } else { + for (ri = -1, cmp = 0; ++ri < bl;) { + if (b[ri] != r[ri]) { + cmp = b[ri] > r[ri] ? 1 : -1; + break; + } + } + } + + // If divisor < remainder, subtract divisor from remainder. + if (cmp < 0) { + + // Remainder can't be more than 1 digit longer than divisor. + // Equalise lengths using divisor with extra leading zero? + for (bt = rl == bl ? b : bz; rl;) { + if (r[--rl] < bt[rl]) { + ri = rl; + for (; ri && !r[--ri];) r[ri] = 9; + --r[ri]; + r[rl] += 10; + } + r[rl] -= bt[rl]; + } + + for (; !r[0];) r.shift(); + } else { + break; + } + } + + // Add the digit n to the result array. + qc[qi++] = cmp ? n : ++n; + + // Update the remainder. + if (r[0] && cmp) r[rl] = a[ai] || 0; + else r = [a[ai]]; + + } while ((ai++ < al || r[0] !== UNDEFINED) && k--); + + // Leading zero? Do not remove if result is simply zero (qi == 1). + if (!qc[0] && qi != 1) { + + // There can't be more than one zero. + qc.shift(); + q.e--; + p--; + } + + // Round? + if (qi > p) round(q, p, Big.RM, r[0] !== UNDEFINED); + + return q; + }; + + + /* + * Return true if the value of this Big is equal to the value of Big y, otherwise return false. + */ + P.eq = function (y) { + return this.cmp(y) === 0; + }; + + + /* + * Return true if the value of this Big is greater than the value of Big y, otherwise return + * false. + */ + P.gt = function (y) { + return this.cmp(y) > 0; + }; + + + /* + * Return true if the value of this Big is greater than or equal to the value of Big y, otherwise + * return false. + */ + P.gte = function (y) { + return this.cmp(y) > -1; + }; + + + /* + * Return true if the value of this Big is less than the value of Big y, otherwise return false. + */ + P.lt = function (y) { + return this.cmp(y) < 0; + }; + + + /* + * Return true if the value of this Big is less than or equal to the value of Big y, otherwise + * return false. + */ + P.lte = function (y) { + return this.cmp(y) < 1; + }; + + + /* + * Return a new Big whose value is the value of this Big minus the value of Big y. + */ + P.minus = P.sub = function (y) { + var i, j, t, xlty, + x = this, + Big = x.constructor, + a = x.s, + b = (y = new Big(y)).s; + + // Signs differ? + if (a != b) { + y.s = -b; + return x.plus(y); + } + + var xc = x.c.slice(), + xe = x.e, + yc = y.c, + ye = y.e; + + // Either zero? + if (!xc[0] || !yc[0]) { + if (yc[0]) { + y.s = -b; + } else if (xc[0]) { + y = new Big(x); + } else { + y.s = 1; + } + return y; + } + + // Determine which is the bigger number. Prepend zeros to equalise exponents. + if (a = xe - ye) { + + if (xlty = a < 0) { + a = -a; + t = xc; + } else { + ye = xe; + t = yc; + } + + t.reverse(); + for (b = a; b--;) t.push(0); + t.reverse(); + } else { + + // Exponents equal. Check digit by digit. + j = ((xlty = xc.length < yc.length) ? xc : yc).length; + + for (a = b = 0; b < j; b++) { + if (xc[b] != yc[b]) { + xlty = xc[b] < yc[b]; + break; + } + } + } + + // x < y? Point xc to the array of the bigger number. + if (xlty) { + t = xc; + xc = yc; + yc = t; + y.s = -y.s; + } + + /* + * Append zeros to xc if shorter. No need to add zeros to yc if shorter as subtraction only + * needs to start at yc.length. + */ + if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0; + + // Subtract yc from xc. + for (b = i; j > a;) { + if (xc[--j] < yc[j]) { + for (i = j; i && !xc[--i];) xc[i] = 9; + --xc[i]; + xc[j] += 10; + } + + xc[j] -= yc[j]; + } + + // Remove trailing zeros. + for (; xc[--b] === 0;) xc.pop(); + + // Remove leading zeros and adjust exponent accordingly. + for (; xc[0] === 0;) { + xc.shift(); + --ye; + } + + if (!xc[0]) { + + // n - n = +0 + y.s = 1; + + // Result must be zero. + xc = [ye = 0]; + } + + y.c = xc; + y.e = ye; + + return y; + }; + + + /* + * Return a new Big whose value is the value of this Big modulo the value of Big y. + */ + P.mod = function (y) { + var ygtx, + x = this, + Big = x.constructor, + a = x.s, + b = (y = new Big(y)).s; + + if (!y.c[0]) { + throw Error(DIV_BY_ZERO); + } + + x.s = y.s = 1; + ygtx = y.cmp(x) == 1; + x.s = a; + y.s = b; + + if (ygtx) return new Big(x); + + a = Big.DP; + b = Big.RM; + Big.DP = Big.RM = 0; + x = x.div(y); + Big.DP = a; + Big.RM = b; + + return this.minus(x.times(y)); + }; + + + /* + * Return a new Big whose value is the value of this Big negated. + */ + P.neg = function () { + var x = new this.constructor(this); + x.s = -x.s; + return x; + }; + + + /* + * Return a new Big whose value is the value of this Big plus the value of Big y. + */ + P.plus = P.add = function (y) { + var e, k, t, + x = this, + Big = x.constructor; + + y = new Big(y); + + // Signs differ? + if (x.s != y.s) { + y.s = -y.s; + return x.minus(y); + } + + var xe = x.e, + xc = x.c, + ye = y.e, + yc = y.c; + + // Either zero? + if (!xc[0] || !yc[0]) { + if (!yc[0]) { + if (xc[0]) { + y = new Big(x); + } else { + y.s = x.s; + } + } + return y; + } + + xc = xc.slice(); + + // Prepend zeros to equalise exponents. + // Note: reverse faster than unshifts. + if (e = xe - ye) { + if (e > 0) { + ye = xe; + t = yc; + } else { + e = -e; + t = xc; + } + + t.reverse(); + for (; e--;) t.push(0); + t.reverse(); + } + + // Point xc to the longer array. + if (xc.length - yc.length < 0) { + t = yc; + yc = xc; + xc = t; + } + + e = yc.length; + + // Only start adding at yc.length - 1 as the further digits of xc can be left as they are. + for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0; + + // No need to check for zero, as +x + +y != 0 && -x + -y != 0 + + if (k) { + xc.unshift(k); + ++ye; + } + + // Remove trailing zeros. + for (e = xc.length; xc[--e] === 0;) xc.pop(); + + y.c = xc; + y.e = ye; + + return y; + }; + + + /* + * Return a Big whose value is the value of this Big raised to the power n. + * If n is negative, round to a maximum of Big.DP decimal places using rounding + * mode Big.RM. + * + * n {number} Integer, -MAX_POWER to MAX_POWER inclusive. + */ + P.pow = function (n) { + var x = this, + one = new x.constructor('1'), + y = one, + isneg = n < 0; + + if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) { + throw Error(INVALID + 'exponent'); + } + + if (isneg) n = -n; + + for (;;) { + if (n & 1) y = y.times(x); + n >>= 1; + if (!n) break; + x = x.times(x); + } + + return isneg ? one.div(y) : y; + }; + + + /* + * Return a new Big whose value is the value of this Big rounded to a maximum precision of sd + * significant digits using rounding mode rm, or Big.RM if rm is not specified. + * + * sd {number} Significant digits: integer, 1 to MAX_DP inclusive. + * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up). + */ + P.prec = function (sd, rm) { + if (sd !== ~~sd || sd < 1 || sd > MAX_DP) { + throw Error(INVALID + 'precision'); + } + return round(new this.constructor(this), sd, rm); + }; + + + /* + * Return a new Big whose value is the value of this Big rounded to a maximum of dp decimal places + * using rounding mode rm, or Big.RM if rm is not specified. + * If dp is negative, round to an integer which is a multiple of 10**-dp. + * If dp is not specified, round to 0 decimal places. + * + * dp? {number} Integer, -MAX_DP to MAX_DP inclusive. + * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up). + */ + P.round = function (dp, rm) { + if (dp === UNDEFINED) dp = 0; + else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) { + throw Error(INVALID_DP); + } + return round(new this.constructor(this), dp + this.e + 1, rm); + }; + + + /* + * Return a new Big whose value is the square root of the value of this Big, rounded, if + * necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM. + */ + P.sqrt = function () { + var r, c, t, + x = this, + Big = x.constructor, + s = x.s, + e = x.e, + half = new Big('0.5'); + + // Zero? + if (!x.c[0]) return new Big(x); + + // Negative? + if (s < 0) { + throw Error(NAME + 'No square root'); + } + + // Estimate. + s = Math.sqrt(x + ''); + + // Math.sqrt underflow/overflow? + // Re-estimate: pass x coefficient to Math.sqrt as integer, then adjust the result exponent. + if (s === 0 || s === 1 / 0) { + c = x.c.join(''); + if (!(c.length + e & 1)) c += '0'; + s = Math.sqrt(c); + e = ((e + 1) / 2 | 0) - (e < 0 || e & 1); + r = new Big((s == 1 / 0 ? '5e' : (s = s.toExponential()).slice(0, s.indexOf('e') + 1)) + e); + } else { + r = new Big(s + ''); + } + + e = r.e + (Big.DP += 4); + + // Newton-Raphson iteration. + do { + t = r; + r = half.times(t.plus(x.div(t))); + } while (t.c.slice(0, e).join('') !== r.c.slice(0, e).join('')); + + return round(r, (Big.DP -= 4) + r.e + 1, Big.RM); + }; + + + /* + * Return a new Big whose value is the value of this Big times the value of Big y. + */ + P.times = P.mul = function (y) { + var c, + x = this, + Big = x.constructor, + xc = x.c, + yc = (y = new Big(y)).c, + a = xc.length, + b = yc.length, + i = x.e, + j = y.e; + + // Determine sign of result. + y.s = x.s == y.s ? 1 : -1; + + // Return signed 0 if either 0. + if (!xc[0] || !yc[0]) { + y.c = [y.e = 0]; + return y; + } + + // Initialise exponent of result as x.e + y.e. + y.e = i + j; + + // If array xc has fewer digits than yc, swap xc and yc, and lengths. + if (a < b) { + c = xc; + xc = yc; + yc = c; + j = a; + a = b; + b = j; + } + + // Initialise coefficient array of result with zeros. + for (c = new Array(j = a + b); j--;) c[j] = 0; + + // Multiply. + + // i is initially xc.length. + for (i = b; i--;) { + b = 0; + + // a is yc.length. + for (j = a + i; j > i;) { + + // Current sum of products at this digit position, plus carry. + b = c[j] + yc[i] * xc[j - i - 1] + b; + c[j--] = b % 10; + + // carry + b = b / 10 | 0; + } + + c[j] = b; + } + + // Increment result exponent if there is a final carry, otherwise remove leading zero. + if (b) ++y.e; + else c.shift(); + + // Remove trailing zeros. + for (i = c.length; !c[--i];) c.pop(); + y.c = c; + + return y; + }; + + + /* + * Return a string representing the value of this Big in exponential notation rounded to dp fixed + * decimal places using rounding mode rm, or Big.RM if rm is not specified. + * + * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive. + * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up). + */ + P.toExponential = function (dp, rm) { + var x = this, + n = x.c[0]; + + if (dp !== UNDEFINED) { + if (dp !== ~~dp || dp < 0 || dp > MAX_DP) { + throw Error(INVALID_DP); + } + x = round(new x.constructor(x), ++dp, rm); + for (; x.c.length < dp;) x.c.push(0); + } + + return stringify(x, true, !!n); + }; + + + /* + * Return a string representing the value of this Big in normal notation rounded to dp fixed + * decimal places using rounding mode rm, or Big.RM if rm is not specified. + * + * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive. + * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up). + * + * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'. + * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'. + */ + P.toFixed = function (dp, rm) { + var x = this, + n = x.c[0]; + + if (dp !== UNDEFINED) { + if (dp !== ~~dp || dp < 0 || dp > MAX_DP) { + throw Error(INVALID_DP); + } + x = round(new x.constructor(x), dp + x.e + 1, rm); + + // x.e may have changed if the value is rounded up. + for (dp = dp + x.e + 1; x.c.length < dp;) x.c.push(0); + } + + return stringify(x, false, !!n); + }; + + + /* + * Return a string representing the value of this Big. + * Return exponential notation if this Big has a positive exponent equal to or greater than + * Big.PE, or a negative exponent equal to or less than Big.NE. + * Omit the sign for negative zero. + */ + P.toJSON = P.toString = function () { + var x = this, + Big = x.constructor; + return stringify(x, x.e <= Big.NE || x.e >= Big.PE, !!x.c[0]); + }; + + + /* + * Return the value of this Big as a primitve number. + */ + P.toNumber = function () { + var n = Number(stringify(this, true, true)); + if (this.constructor.strict === true && !this.eq(n.toString())) { + throw Error(NAME + 'Imprecise conversion'); + } + return n; + }; + + + /* + * Return a string representing the value of this Big rounded to sd significant digits using + * rounding mode rm, or Big.RM if rm is not specified. + * Use exponential notation if sd is less than the number of digits necessary to represent + * the integer part of the value in normal notation. + * + * sd {number} Significant digits: integer, 1 to MAX_DP inclusive. + * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up). + */ + P.toPrecision = function (sd, rm) { + var x = this, + Big = x.constructor, + n = x.c[0]; + + if (sd !== UNDEFINED) { + if (sd !== ~~sd || sd < 1 || sd > MAX_DP) { + throw Error(INVALID + 'precision'); + } + x = round(new Big(x), sd, rm); + for (; x.c.length < sd;) x.c.push(0); + } + + return stringify(x, sd <= x.e || x.e <= Big.NE || x.e >= Big.PE, !!n); + }; + + + /* + * Return a string representing the value of this Big. + * Return exponential notation if this Big has a positive exponent equal to or greater than + * Big.PE, or a negative exponent equal to or less than Big.NE. + * Include the sign for negative zero. + */ + P.valueOf = function () { + var x = this, + Big = x.constructor; + if (Big.strict === true) { + throw Error(NAME + 'valueOf disallowed'); + } + return stringify(x, x.e <= Big.NE || x.e >= Big.PE, true); + }; + + + // Export + + + Big = _Big_(); + + Big['default'] = Big.Big = Big; + + //AMD. + if (typeof define === 'function' && define.amd) { + define(function () { return Big; }); + + // Node and other CommonJS-like environments that support module.exports. + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = Big; + + //Browser. + } else { + GLOBAL.Big = Big; + } +})(this); diff --git a/app/assets/javascripts/delta_input.js b/app/assets/javascripts/delta_input.js index 0ae7f4a5..5c0da86f 100644 --- a/app/assets/javascripts/delta_input.js +++ b/app/assets/javascripts/delta_input.js @@ -19,7 +19,8 @@ function data_delta_update(el, direction) { var delta = $(el).data('delta'); var granularity = $(el).data('granularity'); - var val = $(el).val(); + var val = $(el).val().replace(',', '.'); + const valueContainedColon = $(el).val() != val; var oldval = $.isNumeric(val) ? Number(val) : 0; var newval = oldval + delta*direction; @@ -31,11 +32,16 @@ function data_delta_update(el, direction) { $('button[data-increment='+id+']').attr('disabled', newval>=max ? 'disabled' : null); // warn when what was entered is not a number - $(el).toggleClass('error', val!='' && val!='.' && (!$.isNumeric(val) || val < 0)); + const erroneousValue = val!='' && val!='.' && (!$.isNumeric(val) || val < 0) + $(el).toggleClass('error', erroneousValue); // update field, unless the user is typing - if (!$(el).is(':focus')) { - $(el).val(round_float(newval, granularity)); + if (!$(el).is(':focus') && !erroneousValue) { + let roundedValue = String(round_float(newval, granularity)); + if (valueContainedColon) { + roundedValue = roundedValue.replace('.', ','); + } + $(el).val(roundedValue); $(el).trigger('changed'); } } diff --git a/app/assets/javascripts/group-order-form.js b/app/assets/javascripts/group-order-form.js new file mode 100644 index 00000000..8e9c6dcd --- /dev/null +++ b/app/assets/javascripts/group-order-form.js @@ -0,0 +1,232 @@ +class GroupOrderForm { + constructor(form$, config) { + this.form$ = form$; + this.articleRows$ = this.form$.find('tr.order-article'); + this.totalPrice$ = this.form$.find('#total_price'); + this.newBalance$ = this.form$.find('#new_balance'); + this.totalBalance$ = this.form$.find('#total_balance'); + this.submitButton$ = this.form$.find('#submit_button'); + this.units = config.units; + this.toleranceIsCostly = config.toleranceIsCostly; + this.groupBalance = config.groupBalance; + this.minimumBalance = config.minimumBalance; + + // TODO: Actually use this!: + this.stockit = config.stockit; + + this.initializeIncreaseDecreaseButtons(); + } + + initializeIncreaseDecreaseButtons() { + this.articleRows$.each((_, element) => this.initializeOrderArticleRow($(element))); + } + + initializeOrderArticleRow(row$) { + const quantity$ = row$.find('.goa-quantity'); + const tolerance$ = row$.find('.goa-tolerance'); + // eslint-disable-next-line no-undef + const quantityAndTolerance$ = mergeJQueryObjects([quantity$, tolerance$]); + // eslint-disable-next-line no-undef + quantityAndTolerance$.each((_, element) => $(element).unitConversionField({ + units: this.units, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + })); + row$.find('.btn-ordering').mousedown((e) => e.preventDefault()); + row$.find('.btn-ordering.decrease').click((event) => this.increaseOrDecrease($(event.target).parents('.btn-group').find('input.numeric'), false)); + row$.find('.btn-ordering.increase').click((event) => this.increaseOrDecrease($(event.target).parents('.btn-group').find('input.numeric'), true)); + + quantityAndTolerance$.change(() => { + this.updateMissingUnits(row$, quantity$); + this.updateBalance(); + }); + quantityAndTolerance$.keyup(() => quantity$.trigger('change')); + } + + updateBalance() { + const total = this.articleRows$ + .toArray() + .reduce((acc, row) => + Big(acc).add(parseFloat($(row).find('*[id^="price_"][id$="_display"]').data('price'))).toNumber(), + 0 + ); + + this.totalPrice$.text(I18n.l('currency', total)); + const balance = Big(this.groupBalance).sub(total).toNumber(); + this.newBalance$.text(I18n.l('currency', balance)); + + // TODO: Figure out why this hidden field is required (Should be + // calculated in the controller IMO!): + this.totalBalance$.val(I18n.l('currency', balance)); + + // determine bgcolor and submit button state according to balance + var bgcolor = ''; + if (balance < this.minimumBalance) { + bgcolor = '#FF0000'; + this.submitButton$.attr('disabled', 'disabled') + } else { + this.submitButton$.removeAttr('disabled') + } + + // update bgcolor + this.articleRows$.find('*[id^="td_price_"]').css('background-color', bgcolor); + } + + increaseOrDecrease(field$, increase) { + let step = parseFloat(field$.attr('step')); + if (isNaN(step)) { + step = 1; + } + if (!increase) { + step *= -1; + } + let value = parseFloat(field$.val()); + + if (isNaN(value)) { + value = 0; + } + + value = round(value + step); + let remainder = round(value % step); + if (remainder !== 0) { + if (!increase) { + remainder *= -1; + } + value += remainder - step; + } + const min = field$.attr('min'); + if (min !== undefined) { + value = Math.max(parseFloat(min), value); + } + + const max = field$.attr('max'); + if (max !== undefined) { + value = Math.min(parseFloat(max), value); + } + + field$.val(value); + field$.trigger('change'); + } + + updateMissingUnits(row$) { + const used$ = row$.find('.quantity .used'); + const unused$ = row$.find('.quantity .unused'); + + const quantity$ = row$.find('.goa-quantity'); + const tolerance$ = row$.find('.goa-tolerance'); + const usedTolerance$ = row$.find('.tolerance .used'); + const unusedTolerance$ = row$.find('.tolerance .unused'); + const totalPacks$ = row$.find('.article-info *[id^="units_"]'); + const totalQuantity$ = row$.find('.article-info *[id^="q_total_"]'); + const totalTolerance$ = row$.find('.article-info *[id^="t_total_"]'); + const totalPrice$ = row$.find('*[id^="price_"][id$="_display"]'); + + const missing$ = row$.find('.missing-units'); + + let quantity = parseFloat(quantity$.val().trim().replace(',', '.')); + if (isNaN(quantity)) { + quantity = 0; + } + const granularity = parseFloat(quantity$.attr('step')); + let tolerance = tolerance$.length === 1 ? parseFloat(tolerance$.val().trim().replace(',', '.')) : 0; + if (isNaN(tolerance)) { + tolerance = 0; + } + const supplierOrderUnit = quantity$.data('supplier-order-unit'); + const converter = quantity$.unitConversionField('getConverter'); + const packSizeDeterminedBySupplierOrderUnit = converter && !converter.isUnitSiConversible(supplierOrderUnit); + + const packSize = packSizeDeterminedBySupplierOrderUnit ? parseFloat(quantity$.data('ratio-group-order-unit-supplier-unit')) : 0.001; + const othersQuantity = parseFloat(quantity$.data('others-quantity')); + const othersTolerance = parseFloat(quantity$.data('others-tolerance')); + const usedQuantity = parseFloat(quantity$.data('used-quantity')); + const minimumOrderQuantity = parseFloat(quantity$.data('minimum-order-quantity')); + const price = parseFloat(quantity$.data('price')); + + const totalQuantity = Big(quantity).add(othersQuantity).toNumber(); + const totalTolerance = Big(tolerance).add(othersTolerance).toNumber(); + + const totalPacks = this.calculatePacks(packSize, totalQuantity, totalTolerance, minimumOrderQuantity) + + const totalPrice = Big(price).mul(Big(quantity).add(this.toleranceIsCostly ? tolerance : 0)).toNumber(); + + // update used/unused quantity + const available = Math.max(0, Big(totalPacks).mul(packSize).sub(othersQuantity).toNumber()); + let used = Math.min(available, quantity); + // ensure that at least the amount of items this group has already been allocated is used + if (quantity >= usedQuantity && used < usedQuantity) { + used = usedQuantity; + } + + const unused = Big(quantity).sub(used).toNumber(); + + const availableForTolerance = quantity < minimumOrderQuantity ? Big(minimumOrderQuantity).sub(quantity).toNumber() : Math.max(0, Big(available).sub(used).sub(othersTolerance).toNumber()); + const usedTolerance = Math.min(availableForTolerance, tolerance); + const unusedTolerance = Big(tolerance).sub(usedTolerance).toNumber(); + + const missing = this.calcMissingItems(packSize, totalQuantity, totalTolerance, minimumOrderQuantity); + + used$.text(round(used)); + unused$.text(round(unused)); + + usedTolerance$.text(round(usedTolerance)); + unusedTolerance$.text(round(unusedTolerance)); + + totalPacks$.text(round(totalPacks)); + + totalPacks$.css('color', this.packCompletedFromTolerance(packSize, totalQuantity, totalTolerance) ? 'grey' : 'auto'); + + totalQuantity$.text(round(totalQuantity)); + totalTolerance$.text(round(totalTolerance)); + totalPrice$.text(I18n.l('currency', round(totalPrice))); + totalPrice$.data('price', round(totalPrice)); + + missing$.text(round(missing)); + if (tolerance$.length === 1) { + this.setRowStyle(row$, missing, granularity, quantity); + } + } + + setRowStyle(row$, missing, granularity, quantity) { + row$.removeClass('missing-many missing-few missing-none'); + if (missing === 0) { + if (quantity !== 0) { + row$.addClass('missing-none'); + } + } else { + row$.addClass(missing <= granularity ? 'missing-few' : 'missing-many'); + } + } + + calculatePacks(packSize, quantity, tolerance, minimumOrderQuantity) { + if (Big(quantity).add(tolerance).toNumber() < minimumOrderQuantity) { + return 0; + } + + const used = Big(quantity).div(packSize).round(0, Big.roundDown).toNumber(); + const remainder = Big(quantity).mod(packSize).toNumber(); + return Big(used).add((remainder > 0) && (Big(remainder).add(tolerance).toNumber() >= packSize) ? 1 : 0).toNumber(); + } + + calcMissingItems(packSize, quantity, tolerance, minimumOrderQuantity) { + if (quantity !== 0 && Big(quantity).add(tolerance).toNumber() < minimumOrderQuantity) { + return Big(minimumOrderQuantity).sub(quantity).sub(tolerance).toNumber(); + } + + if (isNaN(quantity)) { + return quantity; + } + + if (isNaN(packSize)) { + return packSize; + } + + var remainder = Big(quantity).mod(packSize).toNumber(); + return remainder > 0 && Big(remainder).add(tolerance).toNumber() < packSize ? Big(packSize).sub(remainder).sub(tolerance).toNumber() : 0 + } + + packCompletedFromTolerance(packSize, quantity, tolerance) { + var remainder = Big(quantity).mod(packSize).toNumber(); + return (remainder > 0 && (Big(remainder).add(tolerance).toNumber() >= packSize)); + } +} + diff --git a/app/assets/javascripts/migrate-units-form.js b/app/assets/javascripts/migrate-units-form.js new file mode 100644 index 00000000..87d06da6 --- /dev/null +++ b/app/assets/javascripts/migrate-units-form.js @@ -0,0 +1,142 @@ +class MigrateUnitsForm { + constructor(articleUnitRatioTemplate$, table$, units) { + this.articleUnitRatioTemplate$ = articleUnitRatioTemplate$; + this.table$ = table$; + this.units = units; + + this.initializeUnitSelects(); + this.initializeArticlesList(); + } + + loadAvailableUnits() { + this.availableUnits = Object.entries(this.units) + .filter(([, unit]) => unit.visible) + .map(([code, unit]) => ({ key: code, label: unit.name, baseUnit: unit.baseUnit, symbol: unit.symbol, aliases: unit.aliases ? unit.aliases : [] })); + } + + initializeUnitSelects() { + this.loadAvailableUnits(); + this.table$.find('select[name^="samples["]').select2(); + const form = this; + this.table$.find('select[name^="samples["][name$="[supplier_order_unit]"]').each(function() { + form.updateUnitsInSelect(form.availableUnits, $(this)); + form.updateFirstUnitRatioSelect($(this).parents('tr')); + form.updateGroupOrderUnitSelect($(this).parents('tr')); + form.updateFirstUnitRatioQuantity($(this).parents('tr')); + }); + this.table$.find('select[name^="samples["][name$="[supplier_order_unit]"]').change(function() { + form.updateFirstUnitRatioSelect($(this).parents('tr')); + form.updateGroupOrderUnitSelect($(this).parents('tr')); + form.updateFirstUnitRatioQuantity($(this).parents('tr')); + }); + this.table$.find('select[name^="samples["][name$="[first_ratio_unit]"]').change(function() { + form.updateGroupOrderUnitSelect($(this).parents('tr')); + form.updateFirstUnitRatioQuantity($(this).parents('tr')); + }); + } + + updateFirstUnitRatioQuantity(row$) { + const firstRatioSelect$ = row$.find('select[name$="[first_ratio_unit]"]'); + const firstRatioQuantity$ = row$.find('input[name$="[first_ratio_quantity]"]'); + if (firstRatioSelect$.val() === '') { + firstRatioQuantity$.val(''); + firstRatioQuantity$.attr('disabled', 'disabled'); + } else { + firstRatioQuantity$.removeAttr('disabled'); + } + } + + updateGroupOrderUnitSelect(row$) { + const supplierUnitSelect$ = row$.find('select[name$="[supplier_order_unit]"]'); + const firstRatioSelect$ = row$.find('select[name$="[first_ratio_unit]"]'); + const groupOrderUnit$ = row$.find('select[name$="[group_order_unit]"]'); + + const unitsSelectedAbove = []; + const chosenSupplierUnitOption$ = supplierUnitSelect$.find(`option[value="${supplierUnitSelect$.val()}"]`); + unitsSelectedAbove.push({ key: chosenSupplierUnitOption$.val(), label: chosenSupplierUnitOption$.text() }); + + if (firstRatioSelect$.val() != '') { + const chosenFirstRatioOption$ = firstRatioSelect$.find(`option[value="${firstRatioSelect$.val()}"]`); + unitsSelectedAbove.push({ key: chosenFirstRatioOption$.val(), label: chosenFirstRatioOption$.text() }); + } + + const availableUnits = []; + for (const unitSelectedAbove of unitsSelectedAbove) { + availableUnits.push(unitSelectedAbove, ...this.availableUnits.filter(availableUnit => { + if (availableUnit.key === unitSelectedAbove.key) { + return false; + } + + const otherUnit = this.availableUnits.find(unit => unit.key === unitSelectedAbove.key); + return otherUnit !== undefined && otherUnit.baseUnit !== null && availableUnit.baseUnit === otherUnit.baseUnit; + })); + } + + this.updateUnitsInSelect(availableUnits, groupOrderUnit$); + } + + updateFirstUnitRatioSelect(row$) { + const supplierUnitSelect$ = row$.find('select[name$="[supplier_order_unit]"]'); + const firstRatioSelect$ = row$.find('select[name$="[first_ratio_unit]"]'); + const isUnitOrBaseUnitSelected = (unit, select$) => { + const code = select$.val(); + const selectedUnit = this.units[code]; + return unit.key !== code && (!unit.baseUnit || !selectedUnit || !selectedUnit.baseUnit || unit.baseUnit !== selectedUnit.baseUnit); + }; + + const remainingAvailableUnits = this.availableUnits.filter(unit => isUnitOrBaseUnitSelected(unit, supplierUnitSelect$)); + this.updateUnitsInSelect(remainingAvailableUnits, firstRatioSelect$, true); + } + + initializeArticlesList() { + this.table$.find('.articles-list .expander').click(function () { + const currentList$ = $(this).parents('.articles-list'); + currentList$.find('.expander').addClass('d-none'); + currentList$.find('.collapser').removeClass('d-none'); + currentList$.find('.list').removeClass('d-none'); + }); + + this.table$.find('.articles-list .collapser').click(function () { + const currentList$ = $(this).parents('.articles-list'); + currentList$.find('.expander').removeClass('d-none'); + currentList$.find('.collapser').addClass('d-none'); + currentList$.find('.list').addClass('d-none'); + }); + } + + updateUnitsInSelect(units, unitSelect$, includeBlank) { + const valueBeforeUpdate = unitSelect$.val(); + + unitSelect$.empty(); + if (includeBlank) { + unitSelect$.append($(``)); + } + for (const unit of units) { + unitSelect$.append($(``)); + } + + const initialValue = unitSelect$.attr('data-initial-value'); + if (initialValue) { + unitSelect$.val(initialValue); + unitSelect$.removeAttr('data-initial-value'); + } else { + if (unitSelect$.find(`option[value="${valueBeforeUpdate}"]`).length > 0) { + unitSelect$.val(valueBeforeUpdate); + } else { + unitSelect$.val(unitSelect$.find('option:first').val()); + } + } + + unitSelect$.trigger('change'); + + unitSelect$.parents('.control-group').find('.immutable_unit_label').remove(); + if (units.length === 1) { + unitSelect$.parents('.controls').hide(); + unitSelect$.parents('.control-group').append($(`
${units[0].label}
`)) + } else { + unitSelect$.parents('.controls').show(); + } + } + + +} diff --git a/app/assets/javascripts/receive-order-form.js b/app/assets/javascripts/receive-order-form.js new file mode 100644 index 00000000..42b43ef0 --- /dev/null +++ b/app/assets/javascripts/receive-order-form.js @@ -0,0 +1,153 @@ +(function ( $ ) { + class ReceiveOrderForm { + constructor(receiveForm$, packageHelperIcon, newOrderArticlePath) { + this.receiveForm$ = receiveForm$; + this.packageHelperIcon = packageHelperIcon; + this.newOrderArticlePath = newOrderArticlePath; + $(document).on('change keyup', 'input[data-units-expected]', (e) => { + this.updateDelta(e.target); + }); + + $(document).on('touchclick', '#order_articles .unlocker', (e) => this.unlockReceiveInputField($(e.target))); + + $(document).on('click', '#set_all_to_zero', () => { + $('tbody input').each((_, input) => { + $(input).val(0); + this.updateDelta(input); + }); + }); + + $('input[data-units-expected]').each((_, field) => { + this.convertToBillingUnit($(field)); + this.updateDelta(field); + }); + + this.receiveForm$.submit(() => { + $('input[data-units-expected]').each((_, field) => { + this.convertFromBillingUnit($(field)); + }); + }); + + this.initAddArticle('#add_article'); + } + + convertFieldUnit(field$, fromUnit, toUnit) { + const units = parseFloat(field$.val().replace(',', '.')); + if (isNaN(units)) { + return; + } + + const converter = field$.unitConversionField('getConverter'); + if (converter === undefined) { + return units; + } + return converter.getUnitRatio(units, fromUnit, toUnit); + } + + convertToBillingUnit(field$) { + const val = this.convertFieldUnit(field$, field$.data('supplier-order-unit'), field$.data('billing-unit')); + field$.val(val === undefined ? '' : round(val)); + } + + convertFromBillingUnit(field$) { + const convertedValue = this.convertFieldUnit(field$, field$.data('billing-unit'), field$.data('supplier-order-unit')); + const hiddenReceivedField$ = $(``); + this.receiveForm$.append(hiddenReceivedField$); + } + + updateDelta(input) { + const units = $(input).val().replace(',', '.'); + const expected = $(input).data('units-expected'); + const delta = round(units-expected); + let html; + + if (units.replace(/\s/g,"")=="") { + // no value + html = ''; + } else if (isNaN(units)) { + html = ''; + } else if (delta == 0) { + // equal value + html = ''; + } else { + if (delta < 0) { + html = '- '+(-delta)+''; + } else /*if (units> expected)*/ { + html = '+ '+(delta)+''; + } + // show package icon only if the receive field has one + if ($(input).hasClass('package')) { + html += this.packageHelperIcon; + } + } + + $(input).closest('tr').find('.units_delta').html(html); + + // un-dim row when received is nonzero + $(input).closest('tr').toggleClass('unavailable', expected == 0 && html==''); + } + + + replace(id, newRow$) { + const oldRow$ = this.receiveForm$.find(`#order_article_${id}`); + const oldField$ = oldRow$.find('input.units_received') + const currentValue = oldField$.val(); + oldRow$.replaceWith(newRow$); + const newField$ = newRow$.find('input.units_received'); + newField$.val(currentValue); + newField$.unitConversionField(oldField$.unitConversionField('getConversionField')); + this.updateDelta(newField$); + } + + + initAddArticle(sel) { + $(sel).removeAttr('disabled').select2({ + placeholder: I18n.t('orders.receive.add_article'), + formatNoMatches: () => I18n.t('no_articles_available') + // TODO implement adding a new article, like in deliveries + }).on('change', (e) => { + var $input = $(e.target); + var selectedArticleId = $input.val(); + if(!selectedArticleId) { + return false; + } + + $.ajax({ + url: this.newOrderArticlePath, + type: 'post', + data: JSON.stringify({order_article: {article_version: {article_id: selectedArticleId}}}), + contentType: 'application/json; charset=UTF-8' + }); + + $input.val('').trigger('change'); + }); + $(sel).val('').trigger('change'); + } + + unlockReceiveInputField(unlockButton$) { + $('.units_received', unlockButton$.closest('tr')).prop('disabled', false).focus(); + unlockButton$.closest('.input-prepend').prop('title', I18n.t('orders.edit_amount.field_unlocked_title')); + unlockButton$.replaceWith(''); + } + } + + const receiveOrderFormsMap = new Map(); + + $.fn.receiveOrderForm = function (options, actionOptions) { + switch(options) { + case 'replace': { + const receiveOrderForm = receiveOrderFormsMap.get($(this)[0]); + receiveOrderForm.replace(actionOptions.id, actionOptions.newEntry); + break; + } + default: { + const receiveOrderForm = new ReceiveOrderForm($(this), options.packageHelperIcon, options.newOrderArticlePath); + receiveOrderFormsMap.set($(this)[0], receiveOrderForm); + break; + } + } + + return this; + }; + +}( jQuery )); diff --git a/app/assets/javascripts/unit-conversion-field.js b/app/assets/javascripts/unit-conversion-field.js new file mode 100644 index 00000000..7efde41d --- /dev/null +++ b/app/assets/javascripts/unit-conversion-field.js @@ -0,0 +1,251 @@ + + + +(function ( $ ) { + + class UnitConversionField { + constructor(field$, units, popoverTemplate$, useTargetUnitForStep = true, ratios = undefined, supplierOrderUnit = undefined, defaultUnit = undefined, customUnit = undefined) { + this.field$ = field$; + this.popoverTemplate$ = popoverTemplate$; + this.popoverTemplate = this.popoverTemplate$[0].content.querySelector('.popover_contents'); + this.units = units; + this.useTargetUnitForStep = useTargetUnitForStep; + this.ratios = ratios; + this.supplierOrderUnit = supplierOrderUnit; + this.defaultUnit = defaultUnit; + this.customUnit = customUnit; + + this.loadArticleUnitRatios(); + this.unitSelectOptions = this.getUnitSelectOptions(); + + this.converter = new UnitsConverter(this.units, this.ratios, this.supplierOrderUnit); + + // if there's less then two options, don't even bother showing the popover: + if (this.unitSelectOptions.length < 2) { + return; + } + + this.initializeFocusListener(); + } + + loadArticleUnitRatios() { + if (!this.ratios) { + this.ratios = []; + for (let i = 0; this.field$.data(`ratio-quantity-${i}`) !== undefined; i++) { + this.ratios.push({ + quantity: parseFloat(this.field$.data(`ratio-quantity-${i}`)), + unit: this.field$.data(`ratio-unit-${i}`), + }); + } + } + + if (this.supplierOrderUnit === undefined) { + this.supplierOrderUnit = this.field$.data('supplier-order-unit'); + } + if (this.customUnit === undefined) { + this.customUnit = this.field$.data('custom-unit'); + } + if (this.defaultUnit === undefined) { + this.defaultUnit = this.field$.data('default-unit'); + } + } + + initializeFocusListener() { + this.field$.popover({title: this.popoverTemplate.dataset.title, placement: 'bottom', trigger: 'manual'}); + + this.field$.on('focus.unit-conversion-field',() => this.openPopover()); + + this.field$.on('shown.bs.popover', () => this.initializeConversionPopover(this.field$.next('.popover'))); + } + + openPopover() { + $(document).on('mousedown.unit-conversion-field', (e) => { + if ($(e.target).parents('.popover').length !== 0 || e.target === this.field$[0]) { + return; + } + + this.closePopover(); + }); + this.field$.popover('show'); + } + + closePopover() { + $(document).off('mousedown.unit-conversion-field'); + this.field$.off('change.unit-conversion-field'); + this.field$.popover('hide'); + } + + initializeConversionPopover(popover$) { + let showAgainHack = false; + if (!popover$.hasClass('wide')) { + popover$.addClass('wide'); + showAgainHack = true; + } + + const popoverContent$ = popover$.find('.popover-content'); + popoverContent$.empty(); + + const contents$ = $(document.importNode(this.popoverTemplate, true)); + popoverContent$.append(contents$); + + if (showAgainHack) { + this.openPopover(); + return; + } + + this.quantityInput$ = contents$.find('input.quantity'); + this.quantityInput$.val(String(this.field$.val()).replace(',', '.')); + this.applyButton$ = contents$.find('input.apply'); + this.conversionResult$ = contents$.find('.conversion-result'); + this.unitSelect$ = contents$.find('select.unit'); + this.unitSelect$.append(this.unitSelectOptions.map(option => $(``))) + let initialUnitSelectValue = this.defaultUnit; + if (initialUnitSelectValue === undefined) { + initialUnitSelectValue = this.unitSelectOptions[0].value; + } + this.unitSelect$.val(initialUnitSelectValue); + + this.previousUnitSelectValue = convertEmptyStringToUndefined(this.unitSelect$.val()); + + this.unitSelect$.change(() => this.onUnitSelectChanged()); + + // eslint-disable-next-line no-undef + mergeJQueryObjects([this.quantityInput$, this.unitSelect$]).change(() => this.prepareConversion()) + this.quantityInput$.keyup(() => this.quantityInput$.trigger('change')); + + this.field$.on('change.unit-conversion-field', () => { + this.quantityInput$.val(this.field$.val()); + this.quantityInput$.trigger('change') + this.unitSelect$.val(initialUnitSelectValue); + }); + + contents$.find('input.cancel').click(() => this.closePopover()); + this.applyButton$.click(() => { + this.applyConversion(); + this.closePopover(); + }); + + this.onUnitSelectChanged(); + this.prepareConversion(); + } + + getUnitSelectOptions() { + const options = []; + const unit = this.units[this.supplierOrderUnit]; + options.push({ + value: this.supplierOrderUnit, + label: unit === undefined ? this.customUnit : unit.name + }); + if (unit !== undefined) { + options.push(...this.getRelatedUnits(this.supplierOrderUnit)); + } + + + for (const ratio of this.ratios) { + options.push({ + value: ratio.unit, + label: this.units[ratio.unit].name + }); + + options.push(...this.getRelatedUnits(ratio.unit)); + } + + return options; + } + + prepareConversion() { + const unit = this.defaultUnit === undefined ? this.supplierOrderUnit : this.defaultUnit; + const unitLabel = this.unitSelectOptions.find(option => option.value === unit).label; + this.conversionResult$.text('= ' + this.getConversionResult() + ' x ' + unitLabel); + this.conversionResult$.parent().find('.numeric-step-error').remove(); + if (this.quantityInput$.is(':invalid')) { + this.applyButton$.attr('disabled', 'disabled'); + const errorSpan$ = $(`
${I18n.t('errors.step_error', {min: 0, granularity: this.quantityInput$.attr('step')})}
`); + errorSpan$.show(); + this.conversionResult$.after(errorSpan$); + } else { + this.applyButton$.removeAttr('disabled'); + } + } + + applyConversion() { + this.field$ + .val(this.getConversionResult()) + .trigger('change'); + } + + getQuantityInputValue() { + const val = parseFloat(this.quantityInput$.val().trim().replace(',', '.')); + if (isNaN(val)) { + return 0; + } + + return val; + } + + getTargetUnit() { + return this.defaultUnit === undefined ? this.supplierOrderUnit : this.defaultUnit; + } + + getConversionResult() { + const result = this.converter.getUnitRatio(this.getQuantityInputValue(), convertEmptyStringToUndefined(this.unitSelect$.val()), this.getTargetUnit()); + return Big(result).round(4).toNumber(); + } + + onUnitSelectChanged() { + const newValue = this.converter.getUnitRatio(this.getQuantityInputValue(), this.previousUnitSelectValue, convertEmptyStringToUndefined(this.unitSelect$.val())); + this.quantityInput$.val(newValue); + + const selectedUnit = convertEmptyStringToUndefined(this.unitSelect$.val()); + this.previousUnitSelectValue = selectedUnit; + + const step = this.useTargetUnitForStep ? this.converter.getUnitRatio(this.field$.attr('step'), this.getTargetUnit(), selectedUnit) : 0.001; + this.quantityInput$.attr('step', step); + } + + getRelatedUnits(unitId) { + return Object.entries(this.units) + .filter(([id, unit]) => unit.visible && unit.baseUnit !== null && unit.baseUnit === this.units[unitId].baseUnit && id !== unitId) + .map(([id, unit]) => ({ + value: id, + label: unit.name + })); + } + } + + function convertEmptyStringToUndefined(str) { + if (str === '') { + return undefined; + } + + return str; + } + + const convertersMap = new Map(); + + $.fn.unitConversionField = function (optionsOrAction) { + const conversionField = convertersMap.get($(this)[0]); + switch(optionsOrAction) { + case 'getConversionField': + return conversionField; + case 'getConverter': + return conversionField === undefined ? undefined : conversionField.converter; + case 'destroy': + if (conversionField === undefined || conversionField.field$ === undefined) { + break; + } + conversionField.field$.off('focus.unit-conversion-field'); + convertersMap.delete($(this)[0]); + break; + default: { + const converter = new UnitConversionField($(this), optionsOrAction.units, optionsOrAction.popoverTemplate$, optionsOrAction.useTargetUnitForStep, optionsOrAction.ratios, optionsOrAction.supplierOrderUnit, optionsOrAction.defaultUnit, optionsOrAction.customUnit); + convertersMap.set($(this)[0], converter); + break; + } + } + + return this; + }; + + +}( jQuery )); diff --git a/app/assets/javascripts/units-converter.js b/app/assets/javascripts/units-converter.js new file mode 100644 index 00000000..40c65d62 --- /dev/null +++ b/app/assets/javascripts/units-converter.js @@ -0,0 +1,40 @@ +class UnitsConverter { + constructor(units, ratios, supplierOrderUnit) { + this.units = units; + this.ratios = ratios; + this.supplierOrderUnit = supplierOrderUnit; + } + + getUnitQuantity(unitId) { + if (unitId === this.supplierOrderUnit) { + return 1; + } + + const ratio = this.ratios.find(ratio => ratio.unit === unitId); + if (ratio !== undefined) { + return ratio.quantity; + } + + const unit = this.units[unitId]; + const relatedRatio = this.ratios.find(ratio => this.units[ratio.unit].baseUnit === unit.baseUnit); + if (relatedRatio !== undefined) { + const relatedUnit = this.units[relatedRatio.unit]; + return Big(relatedRatio.quantity).div(unit.conversionFactor).mul(relatedUnit.conversionFactor).toNumber(); + } + + const supplierOrderUnitConversionFactor = this.units[this.supplierOrderUnit].conversionFactor; + return Big(supplierOrderUnitConversionFactor).div(unit.conversionFactor).toNumber(); + } + + getUnitRatio(quantity, inputUnit, outputUnit) { + return Big(quantity).div(this.getUnitQuantity(inputUnit)).mul(this.getUnitQuantity(outputUnit)).toNumber(); + } + + isUnitSiConversible(unitId) { + const unit = this.units[unitId]; + if (unit === undefined) { + return false; + } + return !!unit.conversionFactor; + } +} diff --git a/app/assets/stylesheets/article.less b/app/assets/stylesheets/article.less new file mode 100644 index 00000000..a71d7e2f --- /dev/null +++ b/app/assets/stylesheets/article.less @@ -0,0 +1,38 @@ +.extra-unit-fields { + display: none; + position: absolute; + background: #fff; + border: 1px solid #888; + border-radius: 3px; + padding: 10px; + z-index: 999; + width:650px; + box-shadow: 10px 10px 22px 0px rgba(0,0,0,0.25); + + + &.show { + display: block; + } +} + +.extra-unit-fields-btn { + color: #000; +} + +.toggle-extra-units.default-values { + color: #333; + opacity: 0.65; +} + +.toggle-extra-units.show { + color: #333; + opacity: 1; +} + +.sync-table .control-group { + margin-bottom: 0; +} + +.sync-table .extra-unit-fields .control-group { + margin-bottom: 20px; +} diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 971308c9..a46089bf 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -1,5 +1,7 @@ @import "twitter/bootstrap/bootstrap"; @import "twitter/bootstrap/responsive"; +@import "group_order"; +@import "article"; @import "delta_input"; body { @@ -42,6 +44,10 @@ body { @articleUnusedColor: red; @articleUnavailColor: #999; @articleUpdatedColor: #468847; +@articleCustomUnitWarning: #ff8c00; + +// article units: +@unitUntranslatedColor: #999; // dim colors by this amount when the information is less important @nonessentialDim: 35%; @@ -279,6 +285,21 @@ tr.order-article:hover .article-info { display: block; } +tr.order-article { + .group-order-unit { + white-space: nowrap; + } + + .group-order-input { + text-align: center; + + .goa-quantity { + width: 45px; + text-align: right; + } + } +} + // ********* Articles @@ -319,6 +340,12 @@ td.symbol, th.symbol { .partused .symbol { color: tint(@articlePartusedColor, @nonessentialDim); } .unavailable .symbol { color: @articleUnavailColor; } +.article-form .icon-warning-sign { + margin-top: 8px; + margin-left: 10px; + color: @articleCustomUnitWarning; +} + // hide symbols completely on small screens to save space @media (max-width: 768px) { .symbol { @@ -328,6 +355,11 @@ td.symbol, th.symbol { } } +// ********* Article units +tr.untranslated { + color: @unitUntranslatedColor; +} + // ********* Tweaks & fixes @@ -448,6 +480,16 @@ i.package.icon-only { margin: 0; } +// bootstrap 2 doesn't support differnt modal sizes -> let's provide our own modal-xl: +.modal.modal-xl { + width: 80vw; + max-width: 650px; + position: absolute; + left: 0; + right: 0; + margin: 0 auto; +} + // multiple-column layout in forms (landscape tablet and wider only) @media (min-width: 768px) { .form-horizontal .fold-line { @@ -540,3 +582,90 @@ span.negative_amout { .deleted_row { text-decoration: line-through; } + +.form-horizontal .no-indent-left .control-group { + margin-bottom: 0; + .controls { + margin-left: 0; + } +} + +// bootstrap version 2 doesn't support some styles -> let's add makeshift ones manually for now: +.d-flex { + display: flex; +} + +.d-none { + display: none; +} + +.align-items-center { + align-items: center; +} + +.m-0 { + margin:0 !important; +} + +.ml-1 { + margin-left: 3px !important; +} + +.ml-2 { + margin-left: 6px !important; +} + +td.ml-1 > *:first-child { + margin-left: 3px !important; +} + + +.mr-1 { + margin-right: 3px !important; +} + +td.mr-1 > *:last-child { + margin-right: 3px; +} + +.mt-1 { + margin-top: 3px !important; +} + +.mb-1 { + margin-bottom: 3px !important; +} + +.text-decoration-none:hover { + text-decoration: none; +} + +// Wider popovers +.popover.wide { + max-width: 40vw; +} + +.gap-1 { + gap: .25rem; +} + +*[role="button"] { + cursor: pointer; +} + +.user-select-none { + user-select: none; +} + +.numeric-step-error { + display: none; + color: #b94a48; +} + +.btn-group.numeric-step > input:invalid { + border: 1px solid #b94a48; +} + +.btn-group.numeric-step:has(> input:invalid) + .numeric-step-error { + display: block; +} diff --git a/app/assets/stylesheets/delta_input.less b/app/assets/stylesheets/delta_input.less index c0afaf4c..24a38d66 100644 --- a/app/assets/stylesheets/delta_input.less +++ b/app/assets/stylesheets/delta_input.less @@ -6,13 +6,13 @@ form.delta-input, .delta-input form { } .delta-input { - - .btn { + .btn.modify { padding: 4px 0; width: 24px; vertical-align: middle; font-size: 10px; } + input[data-delta] { text-align: center; } diff --git a/app/assets/stylesheets/group_order.less b/app/assets/stylesheets/group_order.less new file mode 100644 index 00000000..3e68a346 --- /dev/null +++ b/app/assets/stylesheets/group_order.less @@ -0,0 +1,16 @@ +.group-order-input { + .btn-group .btn-ordering { + margin-top: 0; + } + + input.goa-quantity, input.goa-tolerance { + height: 14px; + border-radius: 0; + -moz-appearance: textfield; + + &::-webkit-inner-spin-button { + -webkit-appearance: none; + } + } +} + diff --git a/app/assets/stylesheets/main.sass b/app/assets/stylesheets/main.sass index 6a92e70c..189d2d55 100644 --- a/app/assets/stylesheets/main.sass +++ b/app/assets/stylesheets/main.sass @@ -25,7 +25,7 @@ body :color black :border :width 2px - :style solid + :style solid color: $main_red a, a:visited @@ -51,11 +51,11 @@ h1 h2 :font-size 1.4em :margin-top .5em - + h3 :font-size 1em :margin-top 1.5em - + input :color #2e2e2e @@ -121,14 +121,14 @@ option :border-top 1px dotted #ED0606 :color #2e2e2e - + // ******************************** - Logo - head -#header +#header :margin 0 :padding 0 -#logo +#logo background: $main_red :height 1.1em :width 8em @@ -150,45 +150,45 @@ option :font-weight bold :border-top :width 2px - :style dotted + :style dotted color: $main_red - + #logininfo :position absolute :top 3px :right 10px :font-size 1em - ul + ul :list-style none - li + li :margin 0 0 0 5px :float left - a + a :color #737272 :font-weight bold - a:hover + a:hover color: $main_red - + // ************************************* box structure -#main +#main :background #FFF :padding 0 :margin 0 15px 0 15px // ************************************* infobar -#infobar - :width 10% +#infobar + :width 10% :min-width 5em :float right :padding 2.6em 1em :margin 3em 0 0 0 :border-left 1px dotted #ED0606 - :font-size 1.2em + :font-size 1.2em h3 :color #ED0606 - ul + ul :list-style none - li + li :margin .3em 0 0 -3em // ************************************ embedded menu @@ -198,33 +198,33 @@ option :padding 0 10px 0px 5px :float left - ul + ul :list-style-type none :margin 0 0 0.2em 0 :padding 0 - li + li :border-bottom 1px solid #dedede :color #666 :margin 0.8em 0 0 0 - :font-weight bold - a:link, a:visited + :font-weight bold + a:link, a:visited :display block :padding 0.25em 1em :text-decoration none - :width 12em - a:hover, a:focus + :width 12em + a:hover, a:focus :background-color #e3e3e3 - ul + ul :margin 0 :padding 0 - li + li :border-top 1px solid #dedede :border-bottom none :margin 0 :font-weight normal - a:link, a:visited + a:link, a:visited :width 11.5em :padding 0 1em 0.1em 1.5em :font-weight normal @@ -233,9 +233,9 @@ option :position absolute :top 100px :right 1px - + // ************************************** content -#content +#content :padding .5em 0 2.5em 0 :margin 0 :background #FFF @@ -245,28 +245,28 @@ option /************************************ tables -table +table :border-collapse collapse // border2px solid #e3e3e3 :text-align left :width 100% :margin 0 - - thead th, tbody td, th, td - :padding 0.3em - thead tr + thead th, tbody td, th, td + :padding 0.3em + + thead tr :background-color #efefef th :color black tr.odd, tr.even - :border-top 1px solid #DDDDDD + :border-top 1px solid #DDDDDD tr.odd, tr.odd input :background-color #F6F6F6 tr.even :background-color #FBFBFB tr.unavailable, tr.unavailable a - :color grey + :color grey tr.unavailable a:hover :color #ED0606 tr.just_updated @@ -279,14 +279,14 @@ table color: green tr.failed color: red - + table.list //:border 2px solid #78b74e tr :border 1px solid #e3e3e3 tbody tr:hover :background-color #EEEEDD - + table tfoot tr :background-color #fff td @@ -297,12 +297,12 @@ tr.edit_inline td, span :padding 0.5em 0.2em -div.legend, div.legend table th +div.legend, div.legend table th :color grey :font-size .8em :background none -form table +form table :border none table.ordered_articles @@ -316,7 +316,7 @@ table.ordered_articles table tfoot :font-weight bold - + td.currency, td.actions :text-align right :padding-right 0.5em @@ -336,7 +336,7 @@ div.edit_form :padding 0 0.8em 0.8em 0.8em :margin 5px 0 :color black - + #edit_article, #edit_box, #ajax_box :position fixed :top 5em @@ -345,22 +345,22 @@ div.edit_form :background #FBFBFB :padding 3em :padding-top 1em - :border + :border :width 3px :style solid color: $main_red - + // ***************************************** other boxes */ // *********boxes in columns ****/ -div.box +div.box :border-left 2px solid #78b74e :padding-left 5px -div.single_column +div.single_column :width 100% -div.left_column +div.left_column :width 40% :float left @@ -376,22 +376,22 @@ div.right_column // *********** content of boxes ******/ -div.box_title +div.box_title :background #78b74e :padding 5px 10px - - h2, h2 a + + h2, h2 a :color #FFF :margin 0 h2 - :font-size 1.3em + :font-size 1.3em -div.column_content +div.column_content background: $boxContent :color black :padding 10px margin-bottom: 2em - h2 + h2 :color black :font-size 1.3em :margin 1em 0 0 0 @@ -407,16 +407,16 @@ div.column_content // * article show tr.current_price :background #cdee9e - + // **** maybe later for the very little spinner li.check div.spinner - :display block - :height 5px - :width 21px + :display block + :height 5px + :width 21px :background-image image-url('dots-white.gif') - :line-height 16px - :float left + :line-height 16px + :float left :margin-right 5px :background-position center center :background-repeat no-repeat @@ -425,12 +425,12 @@ li.check div.spinner // ************************************* the order page */ span.used, span.unused :font-weight bold -span.used +span.used :color #008000 span.unused :color #ed0606 span.total - :font-size 80% + :font-size 80% table#order :text-align center input @@ -513,7 +513,7 @@ tr.order-article:hover .article-info .timestamp :font-size 0.8em :color grey - + // *************** Clearing Order Page .. #editOrderNav a @@ -526,7 +526,7 @@ tr.order-article:hover .article-info :display inline :list-style none :margin-right 1em - + // *************** Tasks ... .accepted color: green @@ -534,7 +534,7 @@ tr.order-article:hover .article-info .done, .done a, .done .accepted color: grey font-weight: normal - + // ************** auto_complete ul.autocomplete .nick, .informal @@ -544,7 +544,7 @@ ul.autocomplete .informal color: grey margin-left: 1em - + // ******* to navigate easy to the next element, e.g. next order #element_navigation position: relative @@ -559,7 +559,7 @@ ul.autocomplete background-color: #fff text-align: center margin: 0 10px 10px 0 - + // *** wiki #wiki_content border-style: none diff --git a/app/controllers/admin/bank_accounts_controller.rb b/app/controllers/admin/bank_accounts_controller.rb index a54ea798..ac6bb64f 100644 --- a/app/controllers/admin/bank_accounts_controller.rb +++ b/app/controllers/admin/bank_accounts_controller.rb @@ -3,39 +3,39 @@ class Admin::BankAccountsController < Admin::BaseController def new @bank_account = BankAccount.new(params[:bank_account]) - render :layout => false + render layout: false + end + + def edit + @bank_account = BankAccount.find(params[:id]) + render action: 'new', layout: false end def create @bank_account = BankAccount.new(params[:bank_account]) if @bank_account.valid? && @bank_account.save - redirect_to update_bank_accounts_admin_finances_url, :status => 303 + redirect_to update_bank_accounts_admin_finances_url, status: :see_other else - render :action => 'new', :layout => false + render action: 'new', layout: false end end - def edit - @bank_account = BankAccount.find(params[:id]) - render :action => 'new', :layout => false - end - def update @bank_account = BankAccount.find(params[:id]) if @bank_account.update_attributes(params[:bank_account]) - redirect_to update_bank_accounts_admin_finances_url, :status => 303 + redirect_to update_bank_accounts_admin_finances_url, status: :see_other else - render :action => 'new', :layout => false + render action: 'new', layout: false end end def destroy @bank_account = BankAccount.find(params[:id]) @bank_account.destroy - redirect_to update_bank_accounts_admin_finances_url, :status => 303 - rescue => error - flash.now[:alert] = error.message + redirect_to update_bank_accounts_admin_finances_url, status: :see_other + rescue StandardError => e + flash.now[:alert] = e.message render template: 'shared/alert' end end diff --git a/app/controllers/admin/bank_gateways_controller.rb b/app/controllers/admin/bank_gateways_controller.rb index 3965c91b..c7ca5516 100644 --- a/app/controllers/admin/bank_gateways_controller.rb +++ b/app/controllers/admin/bank_gateways_controller.rb @@ -6,6 +6,11 @@ def new render layout: false end + def edit + @bank_gateway = BankGateway.find(params[:id]) + render action: 'new', layout: false + end + def create @bank_gateway = BankGateway.new(params[:bank_gateway]) if @bank_gateway.valid? && @bank_gateway.save @@ -15,11 +20,6 @@ def create end end - def edit - @bank_gateway = BankGateway.find(params[:id]) - render action: 'new', layout: false - end - def update @bank_gateway = BankGateway.find(params[:id]) diff --git a/app/controllers/admin/configs_controller.rb b/app/controllers/admin/configs_controller.rb index 516113af..500c1b87 100644 --- a/app/controllers/admin/configs_controller.rb +++ b/app/controllers/admin/configs_controller.rb @@ -1,5 +1,5 @@ class Admin::ConfigsController < Admin::BaseController - before_action :get_tabs, only: [:show, :list] + before_action :get_tabs, only: %i[show list] def show @current_tab = @tabs.include?(params[:tab]) ? params[:tab] : @tabs.first @@ -16,7 +16,7 @@ def list def update parse_recurring_selects! params[:config][:order_schedule] ActiveRecord::Base.transaction do - # TODO support nested configuration keys + # TODO: support nested configuration keys params[:config].each do |key, val| FoodsoftConfig[key] = convert_config_value val end @@ -29,7 +29,7 @@ def update # Set configuration tab names as `@tabs` def get_tabs - @tabs = %w(foodcoop payment tasks messages layout language security others) + @tabs = %w[foodcoop payment tasks messages layout language security others] # allow engines to modify this list engines = Rails::Engine.subclasses.map(&:instance).select { |e| e.respond_to?(:configuration) } engines.each { |e| e.configuration(@tabs, self) } @@ -38,16 +38,16 @@ def get_tabs # turn recurring rules into something palatable def parse_recurring_selects!(config) - if config - for k in [:pickup, :boxfill, :ends] do - if config[k] - # allow clearing it using dummy value '{}' ('' would break recurring_select) - if config[k][:recurr].present? && config[k][:recurr] != '{}' - config[k][:recurr] = ActiveSupport::JSON.decode(config[k][:recurr]) - config[k][:recurr] = FoodsoftDateUtil.rule_from(config[k][:recurr]).to_ical if config[k][:recurr] - else - config[k] = nil - end + return unless config + + for k in %i[pickup boxfill ends] do + if config[k] + # allow clearing it using dummy value '{}' ('' would break recurring_select) + if config[k][:recurr].present? && config[k][:recurr] != '{}' + config[k][:recurr] = ActiveSupport::JSON.decode(config[k][:recurr]) + config[k][:recurr] = FoodsoftDateUtil.rule_from(config[k][:recurr]).to_ical if config[k][:recurr] + else + config[k] = nil end end end diff --git a/app/controllers/admin/finances_controller.rb b/app/controllers/admin/finances_controller.rb index 76a93619..7d2ddff6 100644 --- a/app/controllers/admin/finances_controller.rb +++ b/app/controllers/admin/finances_controller.rb @@ -9,16 +9,16 @@ def index def update_bank_accounts @bank_accounts = BankAccount.order('name') - render :layout => false + render layout: false end def update_bank_gateways @bank_gateways = BankGateway.order('name') - render :layout => false + render layout: false end def update_transaction_types @financial_transaction_classes = FinancialTransactionClass.includes(:financial_transaction_types).order('name ASC') - render :layout => false + render layout: false end end diff --git a/app/controllers/admin/financial_transaction_classes_controller.rb b/app/controllers/admin/financial_transaction_classes_controller.rb index 10359acd..fe46a422 100644 --- a/app/controllers/admin/financial_transaction_classes_controller.rb +++ b/app/controllers/admin/financial_transaction_classes_controller.rb @@ -6,25 +6,25 @@ def new render layout: false end + def edit + @financial_transaction_class = FinancialTransactionClass.find(params[:id]) + render action: 'new', layout: false + end + def create @financial_transaction_class = FinancialTransactionClass.new(params[:financial_transaction_class]) if @financial_transaction_class.save - redirect_to update_transaction_types_admin_finances_url, status: 303 + redirect_to update_transaction_types_admin_finances_url, status: :see_other else render action: 'new', layout: false end end - def edit - @financial_transaction_class = FinancialTransactionClass.find(params[:id]) - render action: 'new', layout: false - end - def update @financial_transaction_class = FinancialTransactionClass.find(params[:id]) if @financial_transaction_class.update_attributes(params[:financial_transaction_class]) - redirect_to update_transaction_types_admin_finances_url, status: 303 + redirect_to update_transaction_types_admin_finances_url, status: :see_other else render action: 'new', layout: false end @@ -33,9 +33,9 @@ def update def destroy @financial_transaction_class = FinancialTransactionClass.find(params[:id]) @financial_transaction_class.destroy! - redirect_to update_transaction_types_admin_finances_url, status: 303 - rescue => error - flash.now[:alert] = error.message + redirect_to update_transaction_types_admin_finances_url, status: :see_other + rescue StandardError => e + flash.now[:alert] = e.message render template: 'shared/alert' end end diff --git a/app/controllers/admin/financial_transaction_types_controller.rb b/app/controllers/admin/financial_transaction_types_controller.rb index 401f31e7..a3b7abeb 100644 --- a/app/controllers/admin/financial_transaction_types_controller.rb +++ b/app/controllers/admin/financial_transaction_types_controller.rb @@ -7,25 +7,25 @@ def new render layout: false end + def edit + @financial_transaction_type = FinancialTransactionType.find(params[:id]) + render action: 'new', layout: false + end + def create @financial_transaction_type = FinancialTransactionType.new(params[:financial_transaction_type]) if @financial_transaction_type.save - redirect_to update_transaction_types_admin_finances_url, status: 303 + redirect_to update_transaction_types_admin_finances_url, status: :see_other else render action: 'new', layout: false end end - def edit - @financial_transaction_type = FinancialTransactionType.find(params[:id]) - render action: 'new', layout: false - end - def update @financial_transaction_type = FinancialTransactionType.find(params[:id]) if @financial_transaction_type.update_attributes(params[:financial_transaction_type]) - redirect_to update_transaction_types_admin_finances_url, status: 303 + redirect_to update_transaction_types_admin_finances_url, status: :see_other else render action: 'new', layout: false end @@ -34,9 +34,9 @@ def update def destroy @financial_transaction_type = FinancialTransactionType.find(params[:id]) @financial_transaction_type.destroy! - redirect_to update_transaction_types_admin_finances_url, status: 303 - rescue => error - flash.now[:alert] = error.message + redirect_to update_transaction_types_admin_finances_url, status: :see_other + rescue StandardError => e + flash.now[:alert] = e.message render template: 'shared/alert' end end diff --git a/app/controllers/admin/mail_delivery_status_controller.rb b/app/controllers/admin/mail_delivery_status_controller.rb index 52a4db92..c0086044 100644 --- a/app/controllers/admin/mail_delivery_status_controller.rb +++ b/app/controllers/admin/mail_delivery_status_controller.rb @@ -3,28 +3,28 @@ class Admin::MailDeliveryStatusController < Admin::BaseController def index @maildeliverystatus = MailDeliveryStatus.order(created_at: :desc) - @maildeliverystatus = @maildeliverystatus.where(email: params[:email]) unless params[:email].blank? + @maildeliverystatus = @maildeliverystatus.where(email: params[:email]) if params[:email].present? @maildeliverystatus = @maildeliverystatus.page(params[:page]).per(@per_page) end def show @maildeliverystatus = MailDeliveryStatus.find(params[:id]) filename = "maildeliverystatus_#{params[:id]}.#{MIME::Types[@maildeliverystatus.attachment_mime].first.preferred_extension}" - send_data(@maildeliverystatus.attachment_data, :filename => filename, :type => @maildeliverystatus.attachment_mime) + send_data(@maildeliverystatus.attachment_data, filename: filename, type: @maildeliverystatus.attachment_mime) end def destroy_all @maildeliverystatus = MailDeliveryStatus.delete_all redirect_to admin_mail_delivery_status_index_path, notice: t('.notice') - rescue => error - redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: e.message) end def destroy @maildeliverystatus = MailDeliveryStatus.find(params[:id]) @maildeliverystatus.destroy redirect_to admin_mail_delivery_status_index_path, notice: t('.notice') - rescue => error - redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: e.message) end end diff --git a/app/controllers/admin/ordergroups_controller.rb b/app/controllers/admin/ordergroups_controller.rb index 279cccdf..4a91f44e 100644 --- a/app/controllers/admin/ordergroups_controller.rb +++ b/app/controllers/admin/ordergroups_controller.rb @@ -5,13 +5,12 @@ def index @ordergroups = Ordergroup.undeleted.order('name ASC') if request.format.csv? - send_data OrdergroupsCsv.new(@ordergroups).to_csv, filename: 'ordergroups.csv', type: 'text/csv' + send_data OrdergroupsCsv.new(@ordergroups).to_csv, filename: 'ordergroups.csv', + type: 'text/csv' end # if somebody uses the search field: - unless params[:query].blank? - @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%") - end + @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present? @ordergroups = @ordergroups.page(params[:page]).per(@per_page) end @@ -19,8 +18,8 @@ def index def destroy @ordergroup = Ordergroup.find(params[:id]) @ordergroup.mark_as_deleted - redirect_to admin_ordergroups_url, notice: t('admin.ordergroups.destroy.notice') - rescue => error - redirect_to admin_ordergroups_url, alert: t('admin.ordergroups.destroy.error') + redirect_to admin_ordergroups_url, notice: t('.notice') + rescue StandardError => e + redirect_to admin_ordergroups_url, alert: t('.error') end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 97ff4177..132051ee 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -5,12 +5,10 @@ def index @users = params[:show_deleted] ? User.deleted : User.undeleted @users = @users.includes(:mail_delivery_status) - if request.format.csv? - send_data UsersCsv.new(@users).to_csv, filename: 'users.csv', type: 'text/csv' - end + send_data UsersCsv.new(@users).to_csv, filename: 'users.csv', type: 'text/csv' if request.format.csv? # if somebody uses the search field: - @users = @users.natural_search(params[:user_name]) unless params[:user_name].blank? + @users = @users.natural_search(params[:user_name]) if params[:user_name].present? @users = @users.natural_order.page(params[:page]).per(@per_page) end @@ -18,17 +16,17 @@ def index def destroy @user = User.find(params[:id]) @user.mark_as_deleted - redirect_to admin_users_url, notice: t('admin.users.destroy.notice') - rescue => error - redirect_to admin_users_url, alert: t('admin.users.destroy.error', error: error.message) + redirect_to admin_users_url, notice: t('.notice') + rescue StandardError => e + redirect_to admin_users_url, alert: t('.error', error: e.message) end def restore @user = User.find(params[:id]) @user.restore - redirect_to admin_users_url, notice: t('admin.users.restore.notice') - rescue => error - redirect_to admin_users_url, alert: t('admin.users.restore.error', error: error.message) + redirect_to admin_users_url, notice: t('.notice') + rescue StandardError => e + redirect_to admin_users_url, alert: t('.error', error: e.message) end def sudo diff --git a/app/controllers/admin/workgroups_controller.rb b/app/controllers/admin/workgroups_controller.rb index 184000bd..f5a9c2a3 100644 --- a/app/controllers/admin/workgroups_controller.rb +++ b/app/controllers/admin/workgroups_controller.rb @@ -4,7 +4,7 @@ class Admin::WorkgroupsController < Admin::BaseController def index @workgroups = Workgroup.order('name ASC') # if somebody uses the search field: - @workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].blank? + @workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present? @workgroups = @workgroups.page(params[:page]).per(@per_page) end @@ -12,8 +12,8 @@ def index def destroy @workgroup = Workgroup.find(params[:id]) @workgroup.destroy - redirect_to admin_workgroups_url, notice: t('admin.workgroups.destroy.notice') - rescue => error - redirect_to admin_workgroups_url, alert: t('admin.workgroups.destroy.error', error: error.message) + redirect_to admin_workgroups_url, notice: t('.notice') + rescue StandardError => e + redirect_to admin_workgroups_url, alert: t('.error', error: e.message) end end diff --git a/app/controllers/api/v1/articles_controller.rb b/app/controllers/api/v1/articles_controller.rb new file mode 100644 index 00000000..67850fee --- /dev/null +++ b/app/controllers/api/v1/articles_controller.rb @@ -0,0 +1,58 @@ +class Api::V1::ArticlesController < Api::V1::BaseController + skip_before_action :authenticate + + def index + supplier = Supplier.find_by_external_uuid(index_params.fetch(:shared_supplier_uuid)) + raise ActionController::RoutingError, 'Not Found' if supplier.nil? + + @articles = Article.with_latest_versions_and_categories.undeleted.where(supplier_id: supplier, type: nil) + if index_params.include?(:updated_after) + @articles = @articles.where('article_versions.updated_at > ?', + index_params[:updated_after].to_datetime) + end + if index_params.include?(:name) + @articles = @articles.where('article_versions.name LIKE ?', + "%#{index_params[:name]}%") + end + @articles = @articles.where(article_versions: { origin: index_params[:origin] }) if index_params.include?(:origin) + @articles = @articles.page(index_params[:page]).per(index_params.fetch(:per_page)) if index_params.include?(:page) + + data = @articles.map do |article| + version_attributes = article.latest_article_version.attributes + version_attributes.delete_if { |key| key == 'id' || key.end_with?('_id') } + + version_attributes['article_unit_ratios'] = article.latest_article_version.article_unit_ratios.map do |ratio| + ratio_attributes = ratio.attributes + ratio_attributes.delete_if { |key| key == 'id' || key.end_with?('_id') } + end + + version_attributes + end + + latest_update = Article.with_latest_versions_and_categories.undeleted.where(supplier_id: supplier, + type: nil).maximum('article_versions.updated_at') + latest_update = latest_update.utc unless latest_update.nil? + + pagination = nil + if index_params.include?(:page) + current = @articles.current_page + total = @articles.total_pages + per_page = @articles.limit_value + pagination = { + current_page: current, + previous_page: (current > 1 ? (current - 1) : nil), + next_page: (current == total ? nil : (current + 1)), + per_page: per_page, + total_pages: total, + number: @articles.total_count + } + end + render json: { articles: data, pagination: pagination, latest_update: latest_update } + end + + protected + + def index_params + params.permit(:shared_supplier_uuid, :updated_after, :name, :origin, :page, :per_page) + end +end diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 13e903f1..8bed20ec 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -20,29 +20,30 @@ def current_ordergroup def require_ordergroup authenticate - unless current_ordergroup.present? - raise Api::Errors::PermissionRequired.new('Forbidden, must be in an ordergroup') - end + return if current_ordergroup.present? + + raise Api::Errors::PermissionRequired, 'Forbidden, must be in an ordergroup' end def require_minimum_balance minimum_balance = FoodsoftConfig[:minimum_balance] or return - if current_ordergroup.account_balance < minimum_balance - raise Api::Errors::PermissionRequired.new(t('application.controller.error_minimum_balance', min: minimum_balance)) - end + return unless current_ordergroup.account_balance < minimum_balance + + raise Api::Errors::PermissionRequired, t('application.controller.error_minimum_balance', min: minimum_balance) end def require_enough_apples - if current_ordergroup.not_enough_apples? - s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples, stop_ordering_under: FoodsoftConfig[:stop_ordering_under]) - raise Api::Errors::PermissionRequired.new(s) - end + return unless current_ordergroup.not_enough_apples? + + s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples, + stop_ordering_under: FoodsoftConfig[:stop_ordering_under]) + raise Api::Errors::PermissionRequired, s end def require_config_enabled(config) - unless FoodsoftConfig[config] - raise Api::Errors::PermissionRequired.new(t('application.controller.error_not_enabled', config: config)) - end + return if FoodsoftConfig[config] + + raise Api::Errors::PermissionRequired, t('application.controller.error_not_enabled', config: config) end def skip_session @@ -52,12 +53,12 @@ def skip_session def not_found_handler(e) # remove where-clauses from error message (not suitable for end-users) msg = e.message.try { |m| m.sub(/\s*\[.*?\]\s*$/, '') } || 'Not found' - render status: 404, json: { error: 'not_found', error_description: msg } + render status: :not_found, json: { error: 'not_found', error_description: msg } end def not_acceptable_handler(e) msg = e.message || 'Data not acceptable' - render status: 422, json: { error: 'not_acceptable', error_description: msg } + render status: :unprocessable_entity, json: { error: 'not_acceptable', error_description: msg } end def doorkeeper_unauthorized_render_options(error:) @@ -70,11 +71,11 @@ def doorkeeper_forbidden_render_options(error:) def permission_required_handler(e) msg = e.message || 'Forbidden, user has no access' - render status: 403, json: { error: 'forbidden', error_description: msg } + render status: :forbidden, json: { error: 'forbidden', error_description: msg } end # @todo something with ApplicationHelper#show_user - def show_user(user = current_user, **options) + def show_user(user = current_user, **_options) user.display end end diff --git a/app/controllers/api/v1/order_articles_controller.rb b/app/controllers/api/v1/order_articles_controller.rb index 6e7d220d..00c3de18 100644 --- a/app/controllers/api/v1/order_articles_controller.rb +++ b/app/controllers/api/v1/order_articles_controller.rb @@ -14,7 +14,7 @@ def show private def scope - OrderArticle.includes(:article_price, article: :supplier) + OrderArticle.includes(:article_version, article: :supplier) end def search_scope diff --git a/app/controllers/api/v1/user/financial_transactions_controller.rb b/app/controllers/api/v1/user/financial_transactions_controller.rb index 96b32e28..3de38de9 100644 --- a/app/controllers/api/v1/user/financial_transactions_controller.rb +++ b/app/controllers/api/v1/user/financial_transactions_controller.rb @@ -16,7 +16,8 @@ def show def create transaction_type = FinancialTransactionType.find(create_params[:financial_transaction_type_id]) - ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user, transaction_type) + ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user, + transaction_type) render json: ft end diff --git a/app/controllers/api/v1/user/group_order_articles_controller.rb b/app/controllers/api/v1/user/group_order_articles_controller.rb index eeeb122c..1a1ec4d3 100644 --- a/app/controllers/api/v1/user/group_order_articles_controller.rb +++ b/app/controllers/api/v1/user/group_order_articles_controller.rb @@ -4,8 +4,8 @@ class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController before_action -> { doorkeeper_authorize! 'group_orders:user' } before_action :require_ordergroup - before_action :require_minimum_balance, only: [:create, :update] # destroy is ok - before_action :require_enough_apples, only: [:create, :update] # destroy is ok + before_action :require_minimum_balance, only: %i[create update] # destroy is ok + before_action :require_enough_apples, only: %i[create update] # destroy is ok # @todo allow decreasing amounts when minimum balance isn't met def index @@ -35,7 +35,8 @@ def update goa = nil GroupOrderArticle.transaction do goa = scope_for_update.includes(:group_order_article_quantities).find(params.require(:id)) - goa.update_quantities((update_params[:quantity] || goa.quantity).to_i, (update_params[:tolerance] || goa.tolerance).to_i) + goa.update_quantities((update_params[:quantity] || goa.quantity).to_i, + (update_params[:tolerance] || goa.tolerance).to_i) goa.order_article.update_results! goa.group_order.update_price! goa.group_order.update_attributes! updated_by: current_user diff --git a/app/controllers/api/v1/user/ordergroup_controller.rb b/app/controllers/api/v1/user/ordergroup_controller.rb index 08c12b4c..23889fe8 100644 --- a/app/controllers/api/v1/user/ordergroup_controller.rb +++ b/app/controllers/api/v1/user/ordergroup_controller.rb @@ -8,13 +8,13 @@ def financial_overview financial_overview: { account_balance: ordergroup.account_balance.to_f, available_funds: ordergroup.get_available_funds.to_f, - financial_transaction_class_sums: FinancialTransactionClass.sorted.map { |c| + financial_transaction_class_sums: FinancialTransactionClass.sorted.map do |c| { id: c.id, name: c.display, amount: ordergroup["sum_of_class_#{c.id}"].to_f } - } + end } } end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eb90f9b4..3537f8c4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -19,10 +19,10 @@ def self.current private def set_user_last_activity - if current_user && (session[:last_activity] == nil || session[:last_activity] < 1.minutes.ago) - current_user.update_attribute(:last_activity, Time.now) - session[:last_activity] = Time.now - end + return unless current_user && (session[:last_activity].nil? || session[:last_activity] < 1.minute.ago) + + current_user.update_attribute(:last_activity, Time.now) + session[:last_activity] = Time.now end # Many plugins can be turned on and off on the fly with a `use_` configuration option. @@ -64,11 +64,11 @@ def find_supplier end def items_per_page - if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500 - @per_page = params[:per_page].to_i - else - @per_page = 20 - end + @per_page = if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500 + params[:per_page].to_i + else + 20 + end end # Set timezone according to foodcoop preference. diff --git a/app/controllers/article_categories_controller.rb b/app/controllers/article_categories_controller.rb index bfa601d3..810bb3ce 100644 --- a/app/controllers/article_categories_controller.rb +++ b/app/controllers/article_categories_controller.rb @@ -4,17 +4,17 @@ class ArticleCategoriesController < ApplicationController before_action :authenticate_article_meta def create - create!(:notice => I18n.t('article_categories.create.notice')) { article_categories_path } + create!(notice: I18n.t('article_categories.create.notice')) { article_categories_path } end def update - update!(:notice => I18n.t('article_categories.update.notice')) { article_categories_path } + update!(notice: I18n.t('article_categories.update.notice')) { article_categories_path } end def destroy destroy! - rescue => error - redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: error.message) + rescue StandardError => e + redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: e.message) end protected diff --git a/app/controllers/article_units_controller.rb b/app/controllers/article_units_controller.rb new file mode 100644 index 00000000..783ca883 --- /dev/null +++ b/app/controllers/article_units_controller.rb @@ -0,0 +1,63 @@ +class ArticleUnitsController < ApplicationController + before_action :authenticate_article_meta + before_action :load_available_units, only: %i[search create destroy] + + def index; end + + def search + @query = params[:q].blank? ? nil : params[:q].downcase + + existing_article_units = ArticleUnit.all.to_a + @article_units = @available_units + .to_h do |key, value| + [key, value.merge({ code: key, record: existing_article_units.find do |existing_unit| + existing_unit.unit == key + end })] + end + + unless @query.nil? + @article_units = @article_units.select do |_key, value| + (value[:name].downcase.include?(@query) || value[:symbol]&.downcase&.include?(@query)) && + (params[:only_recommended] == '0' || !value[:untranslated]) + end + end + + @article_units = @article_units + .sort { |a, b| sort_by_unit_name(@query, a, b) } + .map { |_key, value| value } + + @article_units = @article_units.take(100) unless @query.nil? + @article_units = @article_units.reject { |unit| unit[:record].nil? } if @query.nil? + end + + def create + @article_unit = ArticleUnit.create(unit: params[:unit]) + end + + def destroy + @article_unit = ArticleUnit.find(params[:id]) + @article_unit.destroy + end + + private + + def load_available_units + @available_units = ArticleUnitsLib.units + end + + def article_unit_params + params.require(:article_unit).permit(:unit) + end + + def sort_by_unit_name(query, a_unit, b_unit) + a_name = a_unit[1][:name].downcase + b_name = b_unit[1][:name].downcase + + unless query.nil? + return -1 if a_name.starts_with?(query) && !b_name.starts_with?(query) + return 1 if !a_name.starts_with?(query) && b_name.starts_with?(query) + end + + a_name <=> b_name + end +end diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb index 5e8c402d..686750f4 100644 --- a/app/controllers/articles_controller.rb +++ b/app/controllers/articles_controller.rb @@ -1,73 +1,89 @@ class ArticlesController < ApplicationController before_action :authenticate_article_meta, :find_supplier + before_action :load_article, only: %i[edit update] + before_action :load_article_units, only: %i[edit update new create sync update_synchronized] + before_action :load_article_categories, only: %i[edit_all migrate_units update_all] + before_action :new_empty_article_ratio, + only: %i[edit edit_all migrate_units update new create parse_upload sync update_synchronized] + def index - if params['sort'] - sort = case params['sort'] - when "name" then "articles.name" - when "unit" then "articles.unit" - when "article_category" then "article_categories.name" - when "note" then "articles.note" - when "availability" then "articles.availability" - when "name_reverse" then "articles.name DESC" - when "unit_reverse" then "articles.unit DESC" - when "article_category_reverse" then "article_categories.name DESC" - when "note_reverse" then "articles.note DESC" - when "availability_reverse" then "articles.availability DESC" + sort = if params['sort'] + case params['sort'] + when 'name' then 'article_versions.name' + when 'unit' then 'article_versions.unit' + when 'article_category' then 'article_categories.name' + when 'note' then 'article_versions.note' + when 'availability' then 'article_versions.availability' + when 'name_reverse' then 'article_versions.name DESC' + when 'unit_reverse' then 'article_versions.unit DESC' + when 'article_category_reverse' then 'article_categories.name DESC' + when 'note_reverse' then 'article_versions.note DESC' + when 'availability_reverse' then 'article_versions.availability DESC' end - else - sort = "article_categories.name, articles.name" - end + else + 'article_categories.name, article_versions.name' + end - @articles = Article.undeleted.where(supplier_id: @supplier, :type => nil).includes(:article_category).order(sort) + @articles = Article.with_latest_versions_and_categories.order(sort).undeleted.where(supplier_id: @supplier, + type: nil) if request.format.csv? send_data ArticlesCsv.new(@articles, encoding: 'utf-8').to_csv, filename: 'articles.csv', type: 'text/csv' return end - @articles = @articles.where('articles.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil? + @articles = @articles.where('article_versions.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil? @articles = @articles.page(params[:page]).per(@per_page) respond_to do |format| format.html - format.js { render :layout => false } + format.js { render layout: false } end end def new - @article = @supplier.articles.build(:tax => FoodsoftConfig[:tax_default]) - render :layout => false + @article = @supplier.articles.build + @article.latest_article_version = @article.article_versions.build + render layout: false end def copy @article = @supplier.articles.find(params[:article_id]).dup - render :layout => false + render layout: false + end + + def edit + render action: 'new', layout: false end def create - @article = Article.new(params[:article]) - if @article.valid? && @article.save - render :layout => false - else - render :action => 'new', :layout => false + valid = false + Article.transaction do + @article = Article.create(supplier_id: @supplier.id) + @article.attributes = { latest_article_version_attributes: params[:article_version] } + raise ActiveRecord::Rollback unless @article.valid? + + valid = @article.save end - end - def edit - @article = Article.find(params[:id]) - render :action => 'new', :layout => false + if valid + render layout: false + else + render action: 'new', layout: false + end end # Updates one Article and highlights the line if succeded def update - @article = Article.find(params[:id]) - - if @article.update_attributes(params[:article]) - render :layout => false - else - render :action => 'new', :layout => false + Article.transaction do + if @article.update_attributes(latest_article_version_attributes: params[:article_version]) + render layout: false + else + Rails.logger.info @article.errors.to_yaml.to_s + render action: 'new', layout: false + end end end @@ -75,31 +91,158 @@ def update def destroy @article = Article.find(params[:id]) @article.mark_as_deleted unless @order = @article.in_open_order # If article is in an active Order, the Order will be returned - render :layout => false + render layout: false end # Renders a form for editing all articles from a supplier def edit_all @articles = @supplier.articles.undeleted + + load_article_units end + def prepare_units_migration; end + + def migrate_units + build_article_migration_samples + end + + def complete_units_migration + @invalid_articles = [] + @samples = [] + + Article.transaction do + params[:samples].values.each do |sample| + next unless sample[:apply_migration] == '1' + + original_unit = nil + articles = Article.with_latest_versions_and_categories + .includes(latest_article_version: [:article_unit_ratios]) + .find(sample[:article_ids]) + articles.each do |article| + latest_article_version = article.latest_article_version + original_unit = latest_article_version.unit + next if latest_article_version.article_unit_ratios.length > 1 || + latest_article_version.billing_unit != latest_article_version.group_order_unit || + latest_article_version.price_unit != latest_article_version.group_order_unit + + article_version_params = sample.slice(:supplier_order_unit, :group_order_granularity, :group_order_unit) + article_version_params[:unit] = nil + article_version_params[:billing_unit] = article_version_params[:group_order_unit] + article_version_params[:price_unit] = article_version_params[:group_order_unit] + article_version_params[:article_unit_ratios_attributes] = {} + if sample[:first_ratio_unit].present? + article_version_params[:article_unit_ratios_attributes]['1'] = { + id: latest_article_version.article_unit_ratios.first&.id, + sort: 1, + quantity: sample[:first_ratio_quantity], + unit: sample[:first_ratio_unit] + } + end + article_version_params[:id] = latest_article_version.id + @invalid_articles << article unless article.update_attributes(latest_article_version_attributes: article_version_params) + end + + errors = articles.find { |a| !a.errors.nil? }&.errors + @samples << { + unit: original_unit, + conversion_result: sample + .except(:article_ids, :first_ratio_quantity, :first_ratio_unit) + .merge( + first_ratio: { + quantity: sample[:first_ratio_quantity], + unit: sample[:first_ratio_unit] + } + ), + articles: articles, + errors: errors, + error: errors.present? + } + end + @supplier.update_attribute(:unit_migration_completed, Time.now) + raise ActiveRecord::Rollback unless @invalid_articles.empty? + end + + if @invalid_articles.empty? + redirect_to supplier_articles_path(@supplier), + notice: I18n.t('articles.controller.complete_units_migration.notice') + else + additional_units = @samples.map do |sample| + [sample[:conversion_result][:supplier_order_unit], sample[:conversion_result][:group_order_unit], + sample[:conversion_result][:first_ratio]&.dig(:unit)] + end.flatten.uniq.compact + load_article_units(additional_units) + + flash.now.alert = I18n.t('articles.controller.error_invalid') + render :migrate_units + end + end + + # def migrate_units + # @articles = @supplier.articles.with_latest_versions_and_categories.undeleted.includes(latest_article_version: [:article_unit_ratios]) + # @original_units = {} + # @articles.each do |article| + # article_version = article.latest_article_version + # quantity = 1 + # ratios = article_version.article_unit_ratios + + # @original_units[article.id] = article_version.unit + + # next if ratios.length > 1 + + # if ratios.length == 1 && ratios[0].quantity != 1 && ratios[0].unit == 'XPP' + # quantity = ratios[0].quantity + # end + + # conversion_result = ArticleUnitsLib.convert_old_unit(article_version.unit, quantity) + + # next if conversion_result.nil? || + # (conversion_result[:first_ratio].nil? && !article_version.article_unit_ratios.empty?) || + # (!conversion_result[:first_ratio].nil? && article_version.article_unit_ratios.length > 1) + + # article_version.unit = nil + # article_version.supplier_order_unit = conversion_result[:supplier_order_unit] + # article_version.group_order_granularity = conversion_result[:group_order_granularity] + # article_version.group_order_unit = conversion_result[:group_order_unit] + + # first_ratio = conversion_result[:first_ratio] + # next if first_ratio.nil? + + # first_ratio = first_ratio.merge(sort: 1) + # first_existing_ratio = article_version.article_unit_ratios[0] + # if first_existing_ratio.nil? + # article_version.article_unit_ratios.build(first_ratio) + # else + # first_existing_ratio.assign_attributes(first_ratio) + # end + # end + + # load_article_units + + # render :edit_all + # end + # Updates all article of specific supplier def update_all invalid_articles = false - begin - Article.transaction do - unless params[:articles].blank? - # Update other article attributes... - @articles = Article.find(params[:articles].keys) - @articles.each do |article| - unless article.update_attributes(params[:articles][article.id.to_s]) - invalid_articles = true unless invalid_articles # Remember that there are validation errors - end + Article.transaction do + if params[:articles].present? + # Update other article attributes... + @articles = Article.with_latest_versions_and_categories + .includes(latest_article_version: [:article_unit_ratios]) + .find(params[:articles].keys) + @articles.each do |article| + article_version_params = params[:articles][article.id.to_s] + article_version_params['id'] = article.latest_article_version.id + unless article.update_attributes(latest_article_version_attributes: article_version_params) + invalid_articles ||= true # Remember that there are validation errors end - - raise ActiveRecord::Rollback if invalid_articles # Rollback all changes end + + @supplier.update_attribute(:unit_migration_completed, Time.now) if params[:complete_migration] + + raise ActiveRecord::Rollback if invalid_articles # Rollback all changes end end @@ -117,7 +260,9 @@ def update_all def update_selected raise I18n.t('articles.controller.error_nosel') if params[:selected_articles].nil? - articles = Article.find(params[:selected_articles]) + articles = Article.with_latest_versions_and_categories + .includes(latest_article_version: [:article_unit_ratios]) + .find(params[:selected_articles]) Article.transaction do case params[:selected_action] when 'destroy' @@ -134,16 +279,15 @@ def update_selected end end # action succeded - redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page]) - rescue => error - redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page]), - :alert => I18n.t('errors.general_msg', :msg => error) + redirect_to supplier_articles_url(@supplier, per_page: params[:per_page]) + rescue StandardError => e + redirect_to supplier_articles_url(@supplier, per_page: params[:per_page]), + alert: I18n.t('errors.general_msg', msg: e) end # lets start with parsing articles from uploaded file, yeah # Renders the upload form - def upload - end + def upload; end # Update articles from a spreadsheet def parse_upload @@ -151,94 +295,159 @@ def parse_upload options = { filename: uploaded_file.original_filename } options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1') options[:convert_units] = (params[:articles]['convert_units'] == '1') - @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, options + @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, + options + + @articles = @updated_article_pairs.pluck(0) + @new_articles + load_article_units + if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty? - redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice') + redirect_to supplier_articles_path(@supplier), + notice: I18n.t('articles.controller.parse_upload.notice') end @ignored_article_count = 0 - rescue => error - redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message) + rescue StandardError => e + redirect_to upload_supplier_articles_path(@supplier), alert: I18n.t('errors.general_msg', msg: e.message) end # sync all articles with the external database # renders a form with articles, which should be updated def sync - # check if there is an shared_supplier - unless @supplier.shared_supplier - redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name) - end - # sync articles against external database - @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_all + @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_remote if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty? - redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice') + redirect_to supplier_articles_path(@supplier), + notice: I18n.t('articles.controller.parse_upload.notice') end + @ignored_article_count = 0 + rescue StandardError => e + redirect_to upload_supplier_articles_path(@supplier), alert: I18n.t('errors.general_msg', msg: e.message) end # Updates, deletes articles when upload or sync form is submitted def update_synchronized - @outlisted_articles = Article.find(params[:outlisted_articles].try(:keys) || []) - @updated_articles = Article.find(params[:articles].try(:keys) || []) - @updated_articles.map { |a| a.assign_attributes(params[:articles][a.id.to_s]) } - @new_articles = (params[:new_articles] || []).map { |a| @supplier.articles.build(a) } + @outlisted_articles = Article.includes(:latest_article_version).where(article_versions: { id: params[:outlisted_articles]&.values || [] }) + @updated_articles = Article.includes(:latest_article_version).where(article_versions: { id: params[:articles]&.values&.map do |v| + v[:id] + end || [] }) + @new_articles = (params[:new_articles]&.values || []).map do |a| + article = @supplier.articles.build + article_version = article.article_versions.build(a) + article.article_versions << article_version + article.latest_article_version = article_version + article_version.article = article + article + end has_error = false Article.transaction do # delete articles begin @outlisted_articles.each(&:mark_as_deleted) - rescue + rescue StandardError # raises an exception when used in current order has_error = true end # Update articles - @updated_articles.each { |a| a.save or has_error = true } + @updated_articles.each_with_index do |a, index| + current_params = params[:articles][index.to_s] + current_params.delete(:id) + + a.latest_article_version.article_unit_ratios.clear + a.latest_article_version.assign_attributes(current_params) + a.save + end or has_error = true # Add new articles @new_articles.each { |a| a.save or has_error = true } raise ActiveRecord::Rollback if has_error end - if !has_error - redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice') - else + if has_error @updated_article_pairs = @updated_articles.map do |article| orig_article = Article.find(article.id) [article, orig_article.unequal_attributes(article)] end flash.now.alert = I18n.t('articles.controller.error_invalid') render params[:from_action] == 'sync' ? :sync : :parse_upload + else + redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice') end end - # renders a view to import articles in local database - # - def shared - # build array of keywords, required for ransack _all suffix - q = params.fetch(:q, {}) - q[:name_cont_all] = params.fetch(:name_cont_all_joined, '').split(' ') - search = @supplier.shared_supplier.shared_articles.ransack(q) - @articles = search.result.page(params[:page]).per(10) - render :layout => false - end + private - # fills a form whith values of the selected shared_article - # when the direct parameter is set and the article is valid, it is imported directly - def import - @article = SharedArticle.find(params[:shared_article_id]).build_new_article(@supplier) - @article.article_category_id = params[:article_category_id] unless params[:article_category_id].blank? - if params[:direct] && !params[:article_category_id].blank? && @article.valid? && @article.save - render :action => 'create', :layout => false - else - render :action => 'new', :layout => false + def build_article_migration_samples + articles = @supplier.articles.with_latest_versions_and_categories.undeleted.includes(latest_article_version: [:article_unit_ratios]) + samples_hash = {} + articles.each do |article| + article_version = article.latest_article_version + quantity = 1 + ratios = article_version.article_unit_ratios + + next if ratios.length > 1 || + article_version.billing_unit != article_version.group_order_unit || + article_version.price_unit != article_version.group_order_unit + + quantity = ratios[0].quantity if ratios.length == 1 && ratios[0].quantity != 1 && ratios[0].unit == 'XPP' + + samples_hash[article_version.unit] = {} if samples_hash[article_version.unit].nil? + samples_hash[article_version.unit][quantity] = [] if samples_hash[article_version.unit][quantity].nil? + samples_hash[article_version.unit][quantity] << article + end + @samples = samples_hash.map do |unit, quantities_hash| + quantities_hash.map do |quantity, sample_articles| + conversion_result = ArticleUnitsLib.convert_old_unit(unit, quantity) + { unit: unit, quantity: quantity, articles: sample_articles, conversion_result: conversion_result } + end end + @samples = @samples.flatten + .reject { |sample| sample[:conversion_result].nil? } + + additional_units = @samples.map do |sample| + [sample[:conversion_result][:supplier_order_unit], sample[:conversion_result][:group_order_unit], + sample[:conversion_result][:first_ratio]&.dig(:unit)] + end.flatten.uniq.compact + load_article_units(additional_units) end - private + def load_article + @article = Article + .with_latest_versions_and_categories + .includes(latest_article_version: [:article_unit_ratios]) + .find(params[:id]) + end + + def load_article_units(additional_units = []) + additional_units = if !@article.nil? + @article.current_article_units + elsif !@articles.nil? + @articles.map(&:current_article_units) + .flatten + .uniq + else + additional_units + end + + @article_units = ArticleUnit.as_options(additional_units: additional_units) + @all_units = ArticleUnit.as_hash(additional_units: additional_units) + end + + def load_article_categories + @article_categories = ArticleCategory.all + end + + def new_empty_article_ratio + @empty_article_unit_ratio = ArticleUnitRatio.new + @empty_article_unit_ratio.article_version = @article.latest_article_version unless @article.nil? + @empty_article_unit_ratio.sort = -1 + end # @return [Number] Number of articles not taken into account when syncing (having no number) def ignored_article_count if action_name == 'sync' || params[:from_action] == 'sync' - @ignored_article_count ||= @supplier.articles.undeleted.where(order_number: [nil, '']).count + @ignored_article_count ||= @supplier.articles.includes(:latest_article_version).undeleted.where(article_versions: { order_number: [ + nil, '' + ] }).count else 0 end diff --git a/app/controllers/concerns/auth.rb b/app/controllers/concerns/auth.rb index 277acd69..f08622ad 100644 --- a/app/controllers/concerns/auth.rb +++ b/app/controllers/concerns/auth.rb @@ -9,15 +9,17 @@ module Concerns::Auth def current_user # check if there is a valid session and return the logged-in user (its object) - if session[:user_id] && params[:foodcoop] - # for shared-host installations. check if the cookie-subdomain fits to request. - @current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope - end + return unless session[:user_id] && params[:foodcoop] + + # for shared-host installations. check if the cookie-subdomain fits to request. + @current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope end def deny_access session[:return_to] = request.original_url - redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path)) + redirect_to root_url, + alert: I18n.t('application.controller.error_denied', + sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path)) end private @@ -47,12 +49,7 @@ def logout def authenticate(role = 'any') # Attempt to retrieve authenticated user from controller instance or session... - if !current_user - # No user at all: redirect to login page. - logout - session[:return_to] = request.original_url - redirect_to_login :alert => I18n.t('application.controller.error_authn') - else + if current_user # We have an authenticated user, now check role... # Roles gets the user through his memberships. hasRole = case role @@ -62,9 +59,9 @@ def authenticate(role = 'any') when 'pickups' then current_user.role_pickups? when 'suppliers' then current_user.role_suppliers? when 'orders' then current_user.role_orders? - when 'finance_or_invoices' then (current_user.role_finance? || current_user.role_invoices?) - when 'finance_or_orders' then (current_user.role_finance? || current_user.role_orders?) - when 'pickups_or_orders' then (current_user.role_pickups? || current_user.role_orders?) + when 'finance_or_invoices' then current_user.role_finance? || current_user.role_invoices? + when 'finance_or_orders' then current_user.role_finance? || current_user.role_orders? + when 'pickups_or_orders' then current_user.role_pickups? || current_user.role_orders? when 'any' then true # no role required else false # any unknown role will always fail end @@ -73,6 +70,11 @@ def authenticate(role = 'any') else deny_access end + else + # No user at all: redirect to login page. + logout + session[:return_to] = request.original_url + redirect_to_login alert: I18n.t('application.controller.error_authn') end end @@ -116,13 +118,13 @@ def authenticate_pickups_or_orders # if fails the user will redirected to startpage def authenticate_membership_or_admin(group_id = params[:id]) @group = Group.find(group_id) - unless @group.member?(@current_user) || @current_user.role_admin? - redirect_to root_path, alert: I18n.t('application.controller.error_members_only') - end + return if @group.member?(@current_user) || @current_user.role_admin? + + redirect_to root_path, alert: I18n.t('application.controller.error_members_only') end def authenticate_or_token(prefix, role = 'any') - if not params[:token].blank? + if params[:token].present? begin TokenVerifier.new(prefix).verify(params[:token]) rescue ActiveSupport::MessageVerifier::InvalidSignature diff --git a/app/controllers/concerns/auth_api.rb b/app/controllers/concerns/auth_api.rb index 2c80dddf..fc16a2c2 100644 --- a/app/controllers/concerns/auth_api.rb +++ b/app/controllers/concerns/auth_api.rb @@ -36,9 +36,9 @@ def doorkeeper_authorize!(*scopes) # Make sure that at least one the given OAuth scopes is valid for the current user's permissions. # @raise Api::Errors::PermissionsRequired def doorkeeper_authorize_roles!(*scopes) - unless scopes.any? { |scope| doorkeeper_scope_permitted?(scope) } - raise Api::Errors::PermissionRequired.new('Forbidden, no permission') - end + return if scopes.any? { |scope| doorkeeper_scope_permitted?(scope) } + + raise Api::Errors::PermissionRequired, 'Forbidden, no permission' end # Check whether a given OAuth scope is permitted for the current user. @@ -48,9 +48,7 @@ def doorkeeper_authorize_roles!(*scopes) def doorkeeper_scope_permitted?(scope) scope_parts = scope.split(':') # user sub-scopes like +config:user+ are always permitted - if scope_parts.last == 'user' - return true - end + return true if scope_parts.last == 'user' case scope_parts.first when 'user' then return true # access to the current user's own profile @@ -64,8 +62,8 @@ def doorkeeper_scope_permitted?(scope) end case scope - when 'orders:read' then return true - when 'orders:write' then return current_user.role_orders? + when 'orders:read' then true + when 'orders:write' then current_user.role_orders? end end end diff --git a/app/controllers/concerns/foodcoop_scope.rb b/app/controllers/concerns/foodcoop_scope.rb index 0a8e382e..7a99adf9 100644 --- a/app/controllers/concerns/foodcoop_scope.rb +++ b/app/controllers/concerns/foodcoop_scope.rb @@ -24,12 +24,12 @@ def select_foodcoop elsif FoodsoftConfig.allowed_foodcoop? foodcoop FoodsoftConfig.select_foodcoop foodcoop else - raise ActionController::RoutingError.new 'Foodcoop Not Found' + raise ActionController::RoutingError, 'Foodcoop Not Found' end end # Always stay in foodcoop url scope - def default_url_options(options = {}) + def default_url_options(_options = {}) super().merge({ foodcoop: FoodsoftConfig.scope }) end end diff --git a/app/controllers/concerns/locale.rb b/app/controllers/concerns/locale.rb index 22686c15..c55d48a2 100644 --- a/app/controllers/concerns/locale.rb +++ b/app/controllers/concerns/locale.rb @@ -30,7 +30,7 @@ def default_language def select_language_according_to_priority language = explicitly_requested_language || session_language || user_settings_language language ||= browser_language unless FoodsoftConfig[:ignore_browser_locale] - language.presence&.to_sym unless language.blank? + language.presence&.to_sym if language.present? end def available_locales @@ -38,11 +38,11 @@ def available_locales end def set_locale - if available_locales.include?(select_language_according_to_priority) - ::I18n.locale = select_language_according_to_priority - else - ::I18n.locale = default_language - end + ::I18n.locale = if available_locales.include?(select_language_according_to_priority) + select_language_according_to_priority + else + default_language + end locale = session[:locale] = ::I18n.locale logger.info("Set locale to #{locale}") diff --git a/app/controllers/concerns/send_order_pdf.rb b/app/controllers/concerns/send_order_pdf.rb index 09225b7c..283512da 100644 --- a/app/controllers/concerns/send_order_pdf.rb +++ b/app/controllers/concerns/send_order_pdf.rb @@ -3,7 +3,7 @@ module Concerns::SendOrderPdf protected - def send_order_pdf order, document + def send_order_pdf(order, document) klass = case document when 'groups' then OrderByGroups when 'articles' then OrderByArticles diff --git a/app/controllers/deliveries_controller.rb b/app/controllers/deliveries_controller.rb index 21ae0c71..955cea82 100644 --- a/app/controllers/deliveries_controller.rb +++ b/app/controllers/deliveries_controller.rb @@ -1,5 +1,5 @@ class DeliveriesController < ApplicationController - before_action :find_supplier, :exclude => :fill_new_stock_article_form + before_action :find_supplier, exclude: :fill_new_stock_article_form def index @deliveries = @supplier.deliveries.order('date DESC') @@ -7,7 +7,7 @@ def index def show @delivery = Delivery.find(params[:id]) - @stock_changes = @delivery.stock_changes.includes(:stock_article).order('articles.name ASC') + @stock_changes = @delivery.stock_changes.includes(stock_article: :latest_article_version).order('article_versions.name ASC') end def new @@ -15,6 +15,10 @@ def new @delivery.date = Date.today # TODO: move to model/database end + def edit + @delivery = Delivery.find(params[:id]) + end + def create @delivery = Delivery.new(params[:delivery]) @@ -22,14 +26,10 @@ def create flash[:notice] = I18n.t('deliveries.create.notice') redirect_to [@supplier, @delivery] else - render :action => "new" + render action: 'new' end end - def edit - @delivery = Delivery.find(params[:id]) - end - def update @delivery = Delivery.find(params[:id]) @@ -37,7 +37,7 @@ def update flash[:notice] = I18n.t('deliveries.update.notice') redirect_to [@supplier, @delivery] else - render :action => "edit" + render action: 'edit' end end @@ -52,18 +52,18 @@ def destroy def add_stock_change @stock_change = StockChange.new @stock_change.stock_article = StockArticle.find(params[:stock_article_id]) - render :layout => false + render layout: false end def form_on_stock_article_create # See publish/subscribe design pattern in /doc. @stock_article = StockArticle.find(params[:id]) - render :layout => false + render layout: false end def form_on_stock_article_update # See publish/subscribe design pattern in /doc. @stock_article = StockArticle.find(params[:id]) - render :layout => false + render layout: false end end diff --git a/app/controllers/feedback_controller.rb b/app/controllers/feedback_controller.rb index ada72859..b4e5ea7e 100644 --- a/app/controllers/feedback_controller.rb +++ b/app/controllers/feedback_controller.rb @@ -1,13 +1,12 @@ class FeedbackController < ApplicationController - def new - end + def new; end def create if params[:message].present? Mailer.feedback(current_user, params[:message]).deliver_now - redirect_to root_url, notice: t('feedback.create.notice') + redirect_to root_url, notice: t('.notice') else - render :action => 'new' + render action: 'new' end end end diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 09c109f8..6f85fd13 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -5,22 +5,22 @@ def index def new @order = Order.find(params[:order_id]) - flash.now.alert = t('finance.balancing.new.alert') if @order.closed? + flash.now.alert = t('.alert') if @order.closed? @comments = @order.comments - @articles = @order.order_articles.ordered_or_member.includes(:article, :article_price, + @articles = @order.order_articles.ordered_or_member.includes(article_version: :article, group_order_articles: { group_order: :ordergroup }) sort_param = params['sort'] || 'name' @articles = case sort_param - when 'name' then - @articles.order('articles.name ASC') - when 'name_reverse' then - @articles.order('articles.name DESC') - when 'order_number' then - @articles.order('articles.order_number ASC') - when 'order_number_reverse' then - @articles.order('articles.order_number DESC') + when 'name' + @articles.order('article_versions.name ASC') + when 'name_reverse' + @articles.order('article_versions.name DESC') + when 'order_number' + @articles.order('article_versions.order_number ASC') + when 'order_number_reverse' + @articles.order('article_versions.order_number DESC') else @articles end @@ -31,13 +31,13 @@ def new def new_on_order_article_create # See publish/subscribe design pattern in /doc. @order_article = OrderArticle.find(params[:order_article_id]) - render :layout => false + render layout: false end def new_on_order_article_update # See publish/subscribe design pattern in /doc. @order_article = OrderArticle.find(params[:order_article_id]) - render :layout => false + render layout: false end def update_summary @@ -46,29 +46,29 @@ def update_summary def edit_note @order = Order.find(params[:id]) - render :layout => false + render layout: false end def update_note @order = Order.find(params[:id]) if @order.update_attributes(params[:order]) - render :layout => false + render layout: false else - render :action => :edit_note, :layout => false + render action: :edit_note, layout: false end end def edit_transport @order = Order.find(params[:id]) - render :layout => false + render layout: false end def update_transport @order = Order.find(params[:id]) @order.update_attributes! params[:order] redirect_to new_finance_order_path(order_id: @order.id) - rescue => error - redirect_to new_finance_order_path(order_id: @order.id), alert: t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to new_finance_order_path(order_id: @order.id), alert: t('errors.general_msg', msg: e.message) end # before the order will booked, a view lists all Ordergroups and its order_prices @@ -81,18 +81,19 @@ def close @order = Order.find(params[:id]) @type = FinancialTransactionType.find_by_id(params.permit(:type)[:type]) @order.close!(@current_user, @type) - redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice') - rescue => error - redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message) + redirect_to finance_order_index_url, notice: t('.notice') + # TODO-article-version + # rescue => error + # redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message) end # Close the order directly, without automaticly updating ordergroups account balances def close_direct @order = Order.find(params[:id]) @order.close_direct!(@current_user) - redirect_to finance_order_index_url, notice: t('finance.balancing.close_direct.notice') - rescue => error - redirect_to finance_order_index_url, alert: t('finance.balancing.close_direct.alert', message: error.message) + redirect_to finance_order_index_url, notice: t('.notice') + rescue StandardError => e + redirect_to finance_order_index_url, alert: t('.alert', message: e.message) end def close_all_direct_with_invoice @@ -103,8 +104,8 @@ def close_all_direct_with_invoice count += 1 end end - redirect_to finance_order_index_url, notice: t('finance.balancing.close_all_direct_with_invoice.notice', count: count) - rescue => error - redirect_to finance_order_index_url, alert: t('errors.general_msg', msg: error.message) + redirect_to finance_order_index_url, notice: t('.notice', count: count) + rescue StandardError => e + redirect_to finance_order_index_url, alert: t('errors.general_msg', msg: e.message) end end diff --git a/app/controllers/finance/bank_accounts_controller.rb b/app/controllers/finance/bank_accounts_controller.rb index 66d9fddd..81403f6a 100644 --- a/app/controllers/finance/bank_accounts_controller.rb +++ b/app/controllers/finance/bank_accounts_controller.rb @@ -8,8 +8,8 @@ def assign_unlinked_transactions @bank_account = BankAccount.find(params[:id]) count = @bank_account.assign_unlinked_transactions redirect_to finance_bank_account_transactions_url(@bank_account), notice: t('.notice', count: count) - rescue => error - redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: e.message) end def import @@ -33,8 +33,8 @@ def import end needs_redirect = ok - rescue => error - flash.alert = t('errors.general_msg', msg: error.message) + rescue StandardError => e + flash.alert = t('errors.general_msg', msg: e.message) needs_redirect = true ensure return unless needs_redirect diff --git a/app/controllers/finance/bank_transactions_controller.rb b/app/controllers/finance/bank_transactions_controller.rb index 53c35168..22b38d06 100644 --- a/app/controllers/finance/bank_transactions_controller.rb +++ b/app/controllers/finance/bank_transactions_controller.rb @@ -3,26 +3,30 @@ class Finance::BankTransactionsController < ApplicationController inherit_resources def index - if params["sort"] - sort = case params["sort"] - when "date" then "date" - when "amount" then "amount" - when "financial_link" then "financial_link_id" - when "date_reverse" then "date DESC" - when "amount_reverse" then "amount DESC" - when "financial_link_reverse" then "financial_link_id DESC" + sort = if params['sort'] + case params['sort'] + when 'date' then 'date' + when 'amount' then 'amount' + when 'financial_link' then 'financial_link_id' + when 'date_reverse' then 'date DESC' + when 'amount_reverse' then 'amount DESC' + when 'financial_link_reverse' then 'financial_link_id DESC' end - else - sort = "date DESC" - end + else + 'date DESC' + end @bank_account = BankAccount.find(params[:bank_account_id]) @bank_transactions_all = @bank_account.bank_transactions.order(sort).includes(:financial_link) - @bank_transactions_all = @bank_transactions_all.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%", "%#{params[:query]}%") unless params[:query].nil? + unless params[:query].nil? + @bank_transactions_all = @bank_transactions_all.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%", + "%#{params[:query]}%") + end @bank_transactions = @bank_transactions_all.page(params[:page]).per(@per_page) respond_to do |format| - format.js; format.html { render } + format.js + format.html { render } format.csv do send_data BankTransactionsCsv.new(@bank_transactions_all).to_csv, filename: 'transactions.csv', type: 'text/csv' end diff --git a/app/controllers/finance/financial_links_controller.rb b/app/controllers/finance/financial_links_controller.rb index 17d8399a..09783640 100644 --- a/app/controllers/finance/financial_links_controller.rb +++ b/app/controllers/finance/financial_links_controller.rb @@ -1,5 +1,5 @@ class Finance::FinancialLinksController < Finance::BaseController - before_action :find_financial_link, except: [:create, :incomplete] + before_action :find_financial_link, except: %i[create incomplete] def show @items = @financial_link.bank_transactions.map do |bt| @@ -37,7 +37,7 @@ def show def create @financial_link = FinancialLink.first_unused_or_create - if params[:bank_transaction] then + if params[:bank_transaction] bank_transaction = BankTransaction.find(params[:bank_transaction]) bank_transaction.update_attribute :financial_link, @financial_link end @@ -72,14 +72,16 @@ def new_financial_transaction def create_financial_transaction financial_transaction = FinancialTransaction.new(financial_transaction_params) - financial_transaction.ordergroup.add_financial_transaction! financial_transaction.amount, financial_transaction.note, current_user, financial_transaction.financial_transaction_type, @financial_link + financial_transaction.ordergroup.add_financial_transaction! financial_transaction.amount, + financial_transaction.note, current_user, financial_transaction.financial_transaction_type, @financial_link redirect_to finance_link_url(@financial_link), notice: t('.notice') - rescue => error - redirect_to finance_link_url(@financial_link), alert: t('errors.general_msg', msg: error) + rescue StandardError => e + redirect_to finance_link_url(@financial_link), alert: t('errors.general_msg', msg: e) end def index_financial_transaction - @financial_transactions = FinancialTransaction.without_financial_link.includes(:financial_transaction_type, :ordergroup) + @financial_transactions = FinancialTransaction.without_financial_link.includes(:financial_transaction_type, + :ordergroup) end def add_financial_transaction diff --git a/app/controllers/finance/financial_transactions_controller.rb b/app/controllers/finance/financial_transactions_controller.rb index 8b9d372d..c1a619a5 100644 --- a/app/controllers/finance/financial_transactions_controller.rb +++ b/app/controllers/finance/financial_transactions_controller.rb @@ -1,22 +1,22 @@ class Finance::FinancialTransactionsController < ApplicationController before_action :authenticate_finance - before_action :find_ordergroup, :except => [:new_collection, :create_collection, :index_collection] + before_action :find_ordergroup, except: %i[new_collection create_collection index_collection] inherit_resources # belongs_to :ordergroup def index - if params['sort'] - sort = case params['sort'] - when "date" then "created_on" - when "note" then "note" - when "amount" then "amount" - when "date_reverse" then "created_on DESC" - when "note_reverse" then "note DESC" - when "amount_reverse" then "amount DESC" + sort = if params['sort'] + case params['sort'] + when 'date' then 'created_on' + when 'note' then 'note' + when 'amount' then 'amount' + when 'date_reverse' then 'created_on DESC' + when 'note_reverse' then 'note DESC' + when 'amount_reverse' then 'amount DESC' end - else - sort = "created_on DESC" - end + else + 'created_on DESC' + end @q = FinancialTransaction.search(params[:q]) @financial_transactions_all = @q.result(distinct: true).includes(:user).order(sort) @@ -26,9 +26,11 @@ def index @financial_transactions = @financial_transactions_all.page(params[:page]).per(@per_page) respond_to do |format| - format.js; format.html { render } + format.js + format.html { render } format.csv do - send_data FinancialTransactionsCsv.new(@financial_transactions_all).to_csv, filename: 'transactions.csv', type: 'text/csv' + send_data FinancialTransactionsCsv.new(@financial_transactions_all).to_csv, filename: 'transactions.csv', + type: 'text/csv' end end end @@ -38,11 +40,11 @@ def index_collection end def new - if @ordergroup - @financial_transaction = @ordergroup.financial_transactions.build - else - @financial_transaction = FinancialTransaction.new - end + @financial_transaction = if @ordergroup + @ordergroup.financial_transactions.build + else + FinancialTransaction.new + end end def create @@ -53,16 +55,18 @@ def create else @financial_transaction.save! end - redirect_to finance_group_transactions_path(@ordergroup), notice: I18n.t('finance.financial_transactions.controller.create.notice') - rescue ActiveRecord::RecordInvalid => error - flash.now[:alert] = error.message - render :action => :new + redirect_to finance_group_transactions_path(@ordergroup), + notice: I18n.t('finance.financial_transactions.controller.create.notice') + rescue ActiveRecord::RecordInvalid => e + flash.now[:alert] = e.message + render action: :new end def destroy transaction = FinancialTransaction.find(params[:id]) transaction.revert!(current_user) - redirect_to finance_group_transactions_path(transaction.ordergroup), notice: t('finance.financial_transactions.controller.destroy.notice') + redirect_to finance_group_transactions_path(transaction.ordergroup), + notice: t('finance.financial_transactions.controller.destroy.notice') end def new_collection @@ -88,17 +92,17 @@ def create_collection params[:financial_transactions].each do |trans| # ignore empty amount fields ... - unless trans[:amount].blank? - amount = trans[:amount].to_f - note = params[:note] - ordergroup = Ordergroup.find(trans[:ordergroup_id]) - if params[:set_balance] - note += " (#{amount})" - amount -= ordergroup.financial_transaction_class_balance(type.financial_transaction_class) - end - ordergroup.add_financial_transaction!(amount, note, @current_user, type, financial_link) - foodcoop_amount -= amount + next if trans[:amount].blank? + + amount = trans[:amount].to_f + note = params[:note] + ordergroup = Ordergroup.find(trans[:ordergroup_id]) + if params[:set_balance] + note += " (#{amount})" + amount -= ordergroup.financial_transaction_class_balance(type.financial_transaction_class) end + ordergroup.add_financial_transaction!(amount, note, @current_user, type, financial_link) + foodcoop_amount -= amount end if params[:create_foodcoop_transaction] @@ -107,7 +111,7 @@ def create_collection user: @current_user, amount: foodcoop_amount, note: params[:note], - financial_link: financial_link, + financial_link: financial_link }) ft.save! end @@ -117,8 +121,8 @@ def create_collection url = financial_link ? finance_link_url(financial_link.id) : finance_ordergroups_url redirect_to url, notice: I18n.t('finance.financial_transactions.controller.create_collection.notice') - rescue => error - flash.now[:alert] = error.message + rescue StandardError => e + flash.now[:alert] = e.message render action: :new_collection end diff --git a/app/controllers/finance/invoices_controller.rb b/app/controllers/finance/invoices_controller.rb index 33949017..64cab6d8 100644 --- a/app/controllers/finance/invoices_controller.rb +++ b/app/controllers/finance/invoices_controller.rb @@ -1,15 +1,16 @@ class Finance::InvoicesController < ApplicationController before_action :authenticate_finance_or_invoices - before_action :find_invoice, only: [:show, :edit, :update, :destroy] - before_action :ensure_can_edit, only: [:edit, :update, :destroy] + before_action :find_invoice, only: %i[show edit update destroy] + before_action :ensure_can_edit, only: %i[edit update destroy] def index @invoices_all = Invoice.includes(:supplier, :deliveries, :orders).order('date DESC') @invoices = @invoices_all.page(params[:page]).per(@per_page) respond_to do |format| - format.js; format.html { render } + format.js + format.html { render } format.csv do send_data InvoicesCsv.new(@invoices_all).to_csv, filename: 'invoices.csv', type: 'text/csv' end @@ -20,11 +21,10 @@ def unpaid @suppliers = Supplier.includes(:invoices).where('invoices.paid_on IS NULL').references(:invoices) end - def show - end + def show; end def new - @invoice = Invoice.new :supplier_id => params[:supplier_id] + @invoice = Invoice.new supplier_id: params[:supplier_id] @invoice.deliveries << Delivery.find_by_id(params[:delivery_id]) if params[:delivery_id] @invoice.orders << Order.find_by_id(params[:order_id]) if params[:order_id] fill_deliveries_and_orders_collection @invoice.id, @invoice.supplier_id @@ -36,12 +36,14 @@ def edit def form_on_supplier_id_change fill_deliveries_and_orders_collection params[:invoice_id], params[:supplier_id] - render :layout => false + render layout: false end def fill_deliveries_and_orders_collection(invoice_id, supplier_id) - @deliveries_collection = Delivery.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, supplier_id).order(date: :desc).limit(25) - @orders_collection = Order.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, supplier_id).order(ends: :desc).limit(25) + @deliveries_collection = Delivery.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, + supplier_id).order(date: :desc).limit(25) + @orders_collection = Order.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, + supplier_id).order(ends: :desc).limit(25) end def create @@ -58,7 +60,7 @@ def create end else fill_deliveries_and_orders_collection @invoice.id, @invoice.supplier_id - render :action => "new" + render action: 'new' end end @@ -81,7 +83,7 @@ def attachment @invoice = Invoice.find(params[:invoice_id]) type = MIME::Types[@invoice.attachment_mime].first filename = "invoice_#{@invoice.id}_attachment.#{type.preferred_extension}" - send_data(@invoice.attachment_data, :filename => filename, :type => type) + send_data(@invoice.attachment_data, filename: filename, type: type) end private @@ -92,8 +94,8 @@ def find_invoice # Returns true if @current_user can edit the invoice.. def ensure_can_edit - unless @invoice.user_can_edit?(current_user) - deny_access - end + return if @invoice.user_can_edit?(current_user) + + deny_access end end diff --git a/app/controllers/finance/ordergroups_controller.rb b/app/controllers/finance/ordergroups_controller.rb index cb661571..a8836f6b 100644 --- a/app/controllers/finance/ordergroups_controller.rb +++ b/app/controllers/finance/ordergroups_controller.rb @@ -1,11 +1,11 @@ class Finance::OrdergroupsController < Finance::BaseController def index - m = /^(?name|sum_of_class_\d+)(?_reverse)?$/.match params["sort"] + m = /^(?name|sum_of_class_\d+)(?_reverse)?$/.match params['sort'] if m sort = m[:col] sort += ' DESC' if m[:reverse] else - sort = "name" + sort = 'name' end @ordergroups = Ordergroup.undeleted.order(sort) diff --git a/app/controllers/foodcoop/ordergroups_controller.rb b/app/controllers/foodcoop/ordergroups_controller.rb index 8aeb977e..a6a6d104 100644 --- a/app/controllers/foodcoop/ordergroups_controller.rb +++ b/app/controllers/foodcoop/ordergroups_controller.rb @@ -2,19 +2,15 @@ class Foodcoop::OrdergroupsController < ApplicationController def index @ordergroups = Ordergroup.undeleted.order('name') - unless params[:name].blank? # Search by name - @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%") - end + @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%") if params[:name].present? # Search by name - if params[:only_active] # Select only active groups - @ordergroups = @ordergroups.active - end + @ordergroups = @ordergroups.active if params[:only_active] # Select only active groups @ordergroups = @ordergroups.page(params[:page]).per(@per_page) respond_to do |format| format.html # index.html.erb - format.js { render :layout => false } + format.js { render layout: false } end end end diff --git a/app/controllers/foodcoop/users_controller.rb b/app/controllers/foodcoop/users_controller.rb index a3a8bd45..c4684126 100644 --- a/app/controllers/foodcoop/users_controller.rb +++ b/app/controllers/foodcoop/users_controller.rb @@ -3,17 +3,18 @@ def index @users = User.undeleted.natural_order # if somebody uses the search field: - @users = @users.natural_search(params[:user_name]) unless params[:user_name].blank? + @users = @users.natural_search(params[:user_name]) if params[:user_name].present? if params[:ordergroup_name] - @users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", "%#{params[:ordergroup_name]}%") + @users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", + "%#{params[:ordergroup_name]}%") end @users = @users.page(params[:page]).per(@per_page) respond_to do |format| format.html # index.html.haml - format.js { render :layout => false } # index.js.erb + format.js { render layout: false } # index.js.erb end end end diff --git a/app/controllers/foodcoop/workgroups_controller.rb b/app/controllers/foodcoop/workgroups_controller.rb index 794c21a0..ce63ac84 100644 --- a/app/controllers/foodcoop/workgroups_controller.rb +++ b/app/controllers/foodcoop/workgroups_controller.rb @@ -1,9 +1,9 @@ class Foodcoop::WorkgroupsController < ApplicationController before_action :authenticate_membership_or_admin, - :except => [:index] + except: [:index] def index - @workgroups = Workgroup.order("name") + @workgroups = Workgroup.order('name') end def edit @@ -13,9 +13,9 @@ def edit def update @workgroup = Workgroup.find(params[:id]) if @workgroup.update_attributes(params[:workgroup]) - redirect_to foodcoop_workgroups_url, :notice => I18n.t('workgroups.update.notice') + redirect_to foodcoop_workgroups_url, notice: I18n.t('workgroups.update.notice') else - render :action => 'edit' + render action: 'edit' end end end diff --git a/app/controllers/group_order_articles_controller.rb b/app/controllers/group_order_articles_controller.rb index d34db7a1..4351b04b 100644 --- a/app/controllers/group_order_articles_controller.rb +++ b/app/controllers/group_order_articles_controller.rb @@ -1,8 +1,8 @@ class GroupOrderArticlesController < ApplicationController before_action :authenticate_finance - before_action :find_group_order_article, except: [:new, :create] + before_action :find_group_order_article, except: %i[new create] - layout false # We only use this controller to server js snippets, no need for layout rendering + layout false # We only use this controller to server js snippets, no need for layout rendering def new @order_article = OrderArticle.find(params[:order_article_id]) @@ -65,7 +65,7 @@ def update_summaries(group_order_article) # Update the price attribute of new GroupOrder group_order_article.group_order.update_price! # Update units_to_order of order_article - group_order_article.order_article.update_results! if group_order_article.order_article.article.is_a?(StockArticle) + group_order_article.order_article.update_results! if group_order_article.order_article.article_version.is_a?(StockArticle) end def find_group_order_article diff --git a/app/controllers/group_orders_controller.rb b/app/controllers/group_orders_controller.rb index 686f0617..e5a442aa 100644 --- a/app/controllers/group_orders_controller.rb +++ b/app/controllers/group_orders_controller.rb @@ -3,9 +3,9 @@ class GroupOrdersController < ApplicationController # Security before_action :ensure_ordergroup_member - before_action :ensure_open_order, :only => [:new, :create, :edit, :update, :order, :stock_order, :saveOrder] - before_action :ensure_my_group_order, only: [:show, :edit, :update] - before_action :enough_apples?, only: [:new, :create] + before_action :ensure_open_order, only: %i[new create edit update order stock_order saveOrder] + before_action :ensure_my_group_order, only: %i[show edit update] + before_action :enough_apples?, only: %i[new create] # Index page. def index @@ -13,9 +13,17 @@ def index @finished_not_closed_orders_including_group_order = Order.finished_not_closed.ordergroup_group_orders_map(@ordergroup) end + def show + @order = @group_order.order + end + def new ordergroup = params[:stock_order] ? nil : @ordergroup - @group_order = @order.group_orders.build(:ordergroup => ordergroup, :updated_by => current_user) + @group_order = @order.group_orders.build(ordergroup: ordergroup, updated_by: current_user) + @ordering_data = @group_order.load_data + end + + def edit @ordering_data = @group_order.load_data end @@ -23,34 +31,26 @@ def create @group_order = GroupOrder.new(params[:group_order]) begin @group_order.save_ordering! - redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.create.notice') + redirect_to group_order_url(@group_order), notice: I18n.t('group_orders.create.notice') rescue ActiveRecord::StaleObjectError - redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_stale') - rescue => exception - logger.error('Failed to update order: ' + exception.message) - redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_general') + redirect_to group_orders_url, alert: I18n.t('group_orders.create.error_stale') + rescue StandardError => e + logger.error('Failed to update order: ' + e.message) + redirect_to group_orders_url, alert: I18n.t('group_orders.create.error_general') end end - def show - @order = @group_order.order - end - - def edit - @ordering_data = @group_order.load_data - end - def update @group_order.attributes = params[:group_order] @group_order.updated_by = current_user begin @group_order.save_ordering! - redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.update.notice') + redirect_to group_order_url(@group_order), notice: I18n.t('group_orders.update.notice') rescue ActiveRecord::StaleObjectError - redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_stale') - rescue => exception - logger.error('Failed to update order: ' + exception.message) - redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_general') + redirect_to group_orders_url, alert: I18n.t('group_orders.update.error_stale') + rescue StandardError => e + logger.error('Failed to update order: ' + e.message) + redirect_to group_orders_url, alert: I18n.t('group_orders.update.error_general') end end @@ -74,16 +74,16 @@ def archive # Used as a :before_action by OrdersController. def ensure_ordergroup_member @ordergroup = @current_user.ordergroup - if @ordergroup.nil? - redirect_to root_url, :alert => I18n.t('group_orders.errors.no_member') - end + return unless @ordergroup.nil? + + redirect_to root_url, alert: I18n.t('group_orders.errors.no_member') end def ensure_open_order - @order = Order.includes([:supplier, :order_articles]).find(order_id_param) + @order = Order.includes(%i[supplier order_articles]).find(order_id_param) unless @order.open? flash[:notice] = I18n.t('group_orders.errors.closed') - redirect_to :action => 'index' + redirect_to action: 'index' end rescue ActiveRecord::RecordNotFound redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound') @@ -91,17 +91,17 @@ def ensure_open_order def ensure_my_group_order @group_order = GroupOrder.find(params[:id]) - if @group_order.ordergroup != @ordergroup && (@group_order.ordergroup || !current_user.role_orders?) - redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound') - end + return unless @group_order.ordergroup != @ordergroup && (@group_order.ordergroup || !current_user.role_orders?) + + redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound') end def enough_apples? - if @ordergroup.not_enough_apples? - redirect_to group_orders_url, - alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples, - stop_ordering_under: FoodsoftConfig[:stop_ordering_under]) - end + return unless @ordergroup.not_enough_apples? + + redirect_to group_orders_url, + alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples, + stop_ordering_under: FoodsoftConfig[:stop_ordering_under]) end def order_id_param diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 6f677b6b..50c4dce7 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -9,8 +9,7 @@ def index @unassigned_tasks = Task.order(:due_date).next_unassigned_tasks_for(current_user) end - def profile - end + def profile; end def reference_calculator if current_user.ordergroup @@ -36,40 +35,43 @@ def ordergroup @user = @current_user @ordergroup = @user.ordergroup - unless @ordergroup.nil? + if @ordergroup.nil? + redirect_to root_path, alert: I18n.t('home.no_ordergroups') + else @ordergroup = Ordergroup.include_transaction_class_sum.find(@ordergroup.id) - if params['sort'] - sort = case params['sort'] - when "date" then "created_on" - when "note" then "note" - when "amount" then "amount" - when "date_reverse" then "created_on DESC" - when "note_reverse" then "note DESC" - when "amount_reverse" then "amount DESC" + sort = if params['sort'] + case params['sort'] + when 'date' then 'created_on' + when 'note' then 'note' + when 'amount' then 'amount' + when 'date_reverse' then 'created_on DESC' + when 'note_reverse' then 'note DESC' + when 'amount_reverse' then 'amount DESC' end - else - sort = "created_on DESC" - end + else + 'created_on DESC' + end @financial_transactions = @ordergroup.financial_transactions.visible.page(params[:page]).per(@per_page).order(sort) - @financial_transactions = @financial_transactions.where('financial_transactions.note LIKE ?', "%#{params[:query]}%") if params[:query].present? + if params[:query].present? + @financial_transactions = @financial_transactions.where('financial_transactions.note LIKE ?', + "%#{params[:query]}%") + end - else - redirect_to root_path, alert: I18n.t('home.no_ordergroups') end end # cancel personal memberships direct from the myProfile-page def cancel_membership - if params[:membership_id] - membership = @current_user.memberships.find!(params[:membership_id]) - else - membership = @current_user.memberships.find_by_group_id!(params[:group_id]) - end + membership = if params[:membership_id] + @current_user.memberships.find!(params[:membership_id]) + else + @current_user.memberships.find_by_group_id!(params[:group_id]) + end membership.destroy - redirect_to my_profile_path, notice: I18n.t('home.ordergroup_cancelled', :group => membership.group.name) + redirect_to my_profile_path, notice: I18n.t('home.ordergroup_cancelled', group: membership.group.name) end protected @@ -82,8 +84,8 @@ def user_params end def ordergroup_params - if params[:user][:ordergroup] - params.require(:user).require(:ordergroup).permit(:contact_address) - end + return unless params[:user][:ordergroup] + + params.require(:user).require(:ordergroup).permit(:contact_address) end end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 37fc757b..266a1de5 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -3,7 +3,7 @@ class InvitesController < ApplicationController before_action -> { require_config_disabled :disable_invite } def new - @invite = Invite.new(:user => @current_user, :group => @group) + @invite = Invite.new(user: @current_user, group: @group) end def create @@ -27,6 +27,10 @@ def create protected def authenticate_membership_or_admin_for_invites - authenticate_membership_or_admin((params[:invite][:group_id] rescue params[:id])) + authenticate_membership_or_admin(begin + params[:invite][:group_id] + rescue StandardError + params[:id] + end) end end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index 052231c5..4c2fd95b 100644 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -1,6 +1,6 @@ class LoginController < ApplicationController skip_before_action :authenticate # no authentication since this is the login page - before_action :validate_token, :only => [:new_password, :update_password] + before_action :validate_token, only: %i[new_password update_password] # Display the form to enter an email address requesting a token to set a new password. def forgot_password @@ -9,20 +9,17 @@ def forgot_password # Sends an email to a user with the token that allows setting a new password through action "password". def reset_password - if request.get? || params[:user].nil? # Catch for get request and give better error message. - redirect_to forgot_password_url, alert: I18n.t('errors.general_again') and return - end + redirect_to forgot_password_url, alert: I18n.t('errors.general_again') and return if request.get? || params[:user].nil? # Catch for get request and give better error message. if (user = User.undeleted.find_by_email(params[:user][:email])) user.request_password_reset! end - redirect_to login_url, :notice => I18n.t('login.controller.reset_password.notice') + redirect_to login_url, notice: I18n.t('login.controller.reset_password.notice') end # Set a new password with a token from the password reminder email. # Called with params :id => User.id and :token => User.reset_password_token to specify a new password. - def new_password - end + def new_password; end # Sets a new password. # Called with params :id => User.id and :token => User.reset_password_token to specify a new password. @@ -32,7 +29,7 @@ def update_password @user.reset_password_token = nil @user.reset_password_expires = nil @user.save - redirect_to login_url, :notice => I18n.t('login.controller.update_password.notice') + redirect_to login_url, notice: I18n.t('login.controller.update_password.notice') else render :new_password end @@ -50,14 +47,14 @@ def accept_invitation @user = User.new(params[:user]) @user.email = @invite.email if @user.save - Membership.new(:user => @user, :group => @invite.group).save! + Membership.new(user: @user, group: @invite.group).save! @invite.destroy session[:locale] = @user.locale redirect_to login_url, notice: I18n.t('login.controller.accept_invitation.notice') end end else - @user = User.new(:email => @invite.email) + @user = User.new(email: @invite.email) end end @@ -65,8 +62,8 @@ def accept_invitation def validate_token @user = User.find_by_id_and_reset_password_token(params[:id], params[:token]) - if (@user.nil? || @user.reset_password_expires < Time.now) - redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid') - end + return unless @user.nil? || @user.reset_password_expires < Time.now + + redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid') end end diff --git a/app/controllers/order_articles_controller.rb b/app/controllers/order_articles_controller.rb index 0552269d..bd5bd33d 100644 --- a/app/controllers/order_articles_controller.rb +++ b/app/controllers/order_articles_controller.rb @@ -1,38 +1,39 @@ class OrderArticlesController < ApplicationController before_action :fetch_order, except: :destroy - before_action :authenticate_finance_or_invoices, except: [:new, :create] - before_action :authenticate_finance_orders_or_pickup, except: [:edit, :update, :destroy] + before_action :authenticate_finance_or_invoices, except: %i[new create] + before_action :authenticate_finance_orders_or_pickup, except: %i[edit update destroy] + before_action :load_order_article, only: %i[edit update] + before_action :load_article_units, only: %i[edit update] + before_action :new_empty_article_ratio, only: %i[edit update] - layout false # We only use this controller to serve js snippets, no need for layout rendering + layout false # We only use this controller to serve js snippets, no need for layout rendering def new @order_article = @order.order_articles.build(params[:order_article]) end + def edit; end + def create # The article may be ordered with zero units - in that case do not complain. # If order_article is ordered and a new order_article is created, an error message will be # given mentioning that the article already exists, which is desired. - @order_article = @order.order_articles.where(:article_id => params[:order_article][:article_id]).first - unless @order_article && @order_article.units_to_order == 0 - @order_article = @order.order_articles.build(params[:order_article]) - end + @order_article = @order.order_articles.joins(:article_version).where(article_versions: { article_id: params[:order_article][:article_version][:article_id] }).first + @order_article = @order.order_articles.build(params[:order_article]) unless @order_article && @order_article.units_to_order == 0 @order_article.save! - rescue + rescue StandardError render action: :new end - def edit - @order_article = OrderArticle.find(params[:id]) - end - def update - @order_article = OrderArticle.find(params[:id]) - begin - @order_article.update_article_and_price!(params[:order_article], params[:article], params[:article_price]) - rescue - render action: :edit - end + # begin + version_params = params.require(:article_version).permit(:id, :unit, :supplier_order_unit, :minimum_order_quantity, + :billing_unit, :group_order_granularity, :group_order_unit, :price, :price_unit, :tax, :deposit, article_unit_ratios_attributes: %i[id sort quantity unit _destroy]) + @order_article.update_handling_versioning!(params[:order_article], version_params) + # TODO-article-version + # rescue + # render action: :edit + # end end def destroy @@ -61,4 +62,20 @@ def authenticate_finance_orders_or_pickup deny_access end + + def load_order_article + @order_article = OrderArticle.includes(article_version: :article_unit_ratios).find(params[:id]) + end + + def load_article_units + additional_units = @order_article&.article_version&.article&.current_article_units || [] + @article_units = ArticleUnit.as_options(additional_units: additional_units) + @all_units = ArticleUnit.as_hash(additional_units: additional_units) + end + + def new_empty_article_ratio + @empty_article_unit_ratio = ArticleUnitRatio.new + @empty_article_unit_ratio.article_version = @order_article.price + @empty_article_unit_ratio.sort = -1 + end end diff --git a/app/controllers/order_comments_controller.rb b/app/controllers/order_comments_controller.rb index 39067577..3583bb0e 100644 --- a/app/controllers/order_comments_controller.rb +++ b/app/controllers/order_comments_controller.rb @@ -1,15 +1,15 @@ class OrderCommentsController < ApplicationController def new @order = Order.find(params[:order_id]) - @order_comment = @order.comments.build(:user => current_user) + @order_comment = @order.comments.build(user: current_user) end def create @order_comment = OrderComment.new(params[:order_comment]) if @order_comment.save - render :layout => false + render layout: false else - render :action => :new, :layout => false + render action: :new, layout: false end end end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 72b10deb..b44ee192 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -5,25 +5,26 @@ class OrdersController < ApplicationController include Concerns::SendOrderPdf before_action :authenticate_pickups_or_orders - before_action :authenticate_orders, except: [:receive, :receive_on_order_article_create, :receive_on_order_article_update, :show] - before_action :remove_empty_article, only: [:create, :update] + before_action :authenticate_orders, + except: %i[receive receive_on_order_article_create receive_on_order_article_update show] + before_action :remove_empty_article, only: %i[create update] # List orders def index @open_orders = Order.open.includes(:supplier) @finished_orders = Order.finished_not_closed.includes(:supplier) @per_page = 15 - if params['sort'] - sort = case params['sort'] - when "supplier" then "suppliers.name, ends DESC" - when "pickup" then "pickup DESC" - when "ends" then "ends DESC" - when "supplier_reverse" then "suppliers.name DESC" - when "ends_reverse" then "ends" + sort = if params['sort'] + case params['sort'] + when 'supplier' then 'suppliers.name, ends DESC' + when 'pickup' then 'pickup DESC' + when 'ends' then 'ends DESC' + when 'supplier_reverse' then 'suppliers.name DESC' + when 'ends_reverse' then 'ends' end - else - sort = "ends DESC" - end + else + 'ends DESC' + end @suppliers = Supplier.having_articles.order('suppliers.name') @orders = Order.closed.includes(:supplier).reorder(sort).page(params[:page]).per(@per_page) end @@ -43,7 +44,7 @@ def show respond_to do |format| format.html format.js do - render :layout => false + render layout: false end format.pdf do send_order_pdf @order, params[:document] @@ -66,8 +67,14 @@ def new else @order = Order.new(supplier_id: params[:supplier_id]).init_dates end - rescue => error - redirect_to orders_url, alert: t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to orders_url, alert: t('errors.general_msg', msg: e.message) + end + + # Page to edit an exsiting order. + # editing finished orders is done in FinanceController + def edit + @order = Order.includes(:articles).find(params[:id]) end # Save a new order. @@ -81,31 +88,25 @@ def create redirect_to @order else logger.debug "[debug] order errors: #{@order.errors.messages}" - render :action => 'new' + render action: 'new' end end - # Page to edit an exsiting order. - # editing finished orders is done in FinanceController - def edit - @order = Order.includes(:articles).find(params[:id]) - end - # Update an existing order. def update @order = Order.find params[:id] if @order.update_attributes params[:order].merge(updated_by: current_user) flash[:notice] = I18n.t('orders.update.notice') - redirect_to :action => 'show', :id => @order + redirect_to action: 'show', id: @order else - render :action => 'edit' + render action: 'edit' end end # Delete an order. def destroy Order.find(params[:id]).destroy - redirect_to :action => 'index' + redirect_to action: 'index' end # Finish a current order. @@ -113,8 +114,9 @@ def finish order = Order.find(params[:id]) order.finish!(@current_user) redirect_to order, notice: I18n.t('orders.finish.notice') - rescue => error - redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message) + # TODO-article-version + # rescue => error + # redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message) end # Send a order to the supplier. @@ -122,20 +124,19 @@ def send_result_to_supplier order = Order.find(params[:id]) order.send_to_supplier!(@current_user) redirect_to order, notice: I18n.t('orders.send_to_supplier.notice') - rescue => error - redirect_to order, alert: I18n.t('errors.general_msg', :msg => error.message) + # TODO-article-version + # rescue => error + # redirect_to order, alert: I18n.t('errors.general_msg', :msg => error.message) end def receive @order = Order.find(params[:id]) - unless request.post? - @order_articles = @order.order_articles.ordered_or_member.includes(:article).order('articles.order_number, articles.name') - else + if request.post? Order.transaction do s = update_order_amounts @order.update_attribute(:state, 'received') if @order.state != 'received' - flash[:notice] = (s ? I18n.t('orders.receive.notice', :msg => s) : I18n.t('orders.receive.notice_none')) + flash[:notice] = (s ? I18n.t('orders.receive.notice', msg: s) : I18n.t('orders.receive.notice_none')) end NotifyReceivedOrderJob.perform_later(@order) if current_user.role_orders? || current_user.role_finance? @@ -145,23 +146,25 @@ def receive else redirect_to receive_order_path(@order) end + else + @order_articles = @order.order_articles.ordered_or_member.includes(:article_version).order('article_versions.order_number, article_versions.name') end end def receive_on_order_article_create # See publish/subscribe design pattern in /doc. @order_article = OrderArticle.find(params[:order_article_id]) - render :layout => false + render layout: false end def receive_on_order_article_update # See publish/subscribe design pattern in /doc. @order_article = OrderArticle.find(params[:order_article_id]) - render :layout => false + render layout: false end protected def update_order_amounts - return if not params[:order_articles] + return unless params[:order_articles] # where to leave remainder during redistribution rest_to = [] @@ -176,30 +179,39 @@ def update_order_amounts # "MySQL lock timeout exceeded" errors. It's ok to do # this article-by-article anway. params[:order_articles].each do |oa_id, oa_params| - unless oa_params.blank? - oa = OrderArticle.find(oa_id) - # update attributes; don't use update_attribute because it calls save - # which makes received_changed? not work anymore - oa.attributes = oa_params - if oa.units_received_changed? - counts[0] += 1 - unless oa.units_received.blank? - cunits[0] += oa.units_received * oa.article.unit_quantity - oacounts = oa.redistribute oa.units_received * oa.price.unit_quantity, rest_to - oacounts.each_with_index { |c, i| cunits[i + 1] += c; counts[i + 1] += 1 if c > 0 } + next if oa_params.blank? + + oa = OrderArticle.find(oa_id) + # update attributes; don't use update_attribute because it calls save + # which makes received_changed? not work anymore + oa.attributes = oa_params + if oa.units_received_changed? + counts[0] += 1 + if oa.units_received.present? + units_received = oa.article_version.convert_quantity(oa.units_received, + oa.article_version.supplier_order_unit, oa.article_version.billing_unit) + cunits[0] += units_received + oacounts = oa.redistribute units_received, rest_to + oacounts.each_with_index do |c, i| + cunits[i + 1] += c + counts[i + 1] += 1 if c > 0 end end - oa.save! end + oa.save! end return nil if counts[0] == 0 notice = [] notice << I18n.t('orders.update_order_amounts.msg1', count: counts[0], units: cunits[0]) - notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1], units: cunits[1]) if params[:rest_to_tolerance] + if params[:rest_to_tolerance] + notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1], + units: cunits[1]) + end notice << I18n.t('orders.update_order_amounts.msg3', count: counts[2], units: cunits[2]) if params[:rest_to_stock] if counts[3] > 0 || cunits[3] > 0 - notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3], units: cunits[3]) + notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3], + units: cunits[3]) end notice.join(', ') end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 5b3d0780..6cc988ab 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -12,16 +12,16 @@ def create user = User.authenticate(params[:nick], params[:password]) if user user.update_attribute(:last_login, Time.now) - login_and_redirect_to_return_to user, :notice => I18n.t('sessions.logged_in') + login_and_redirect_to_return_to user, notice: I18n.t('sessions.logged_in') else flash.now.alert = I18n.t(FoodsoftConfig[:use_nick] ? 'sessions.login_invalid_nick' : 'sessions.login_invalid_email') - render "new" + render 'new' end end def destroy logout - redirect_to login_url, :notice => I18n.t('sessions.logged_out') + redirect_to login_url, notice: I18n.t('sessions.logged_out') end # redirect to root, going to default foodcoop when none given diff --git a/app/controllers/stock_takings_controller.rb b/app/controllers/stock_takings_controller.rb index bdf1dc77..d61488a0 100644 --- a/app/controllers/stock_takings_controller.rb +++ b/app/controllers/stock_takings_controller.rb @@ -7,21 +7,23 @@ def index def new @stock_taking = StockTaking.new - StockArticle.undeleted.each { |a| @stock_taking.stock_changes.build(:stock_article => a) } + StockArticle.with_latest_versions_and_categories.undeleted.each do |a| + @stock_taking.stock_changes.build(stock_article: a) + end end def new_on_stock_article_create # See publish/subscribe design pattern in /doc. stock_article = StockArticle.find(params[:stock_article_id]) - @stock_change = StockChange.new(:stock_article => stock_article) + @stock_change = StockChange.new(stock_article: stock_article) - render :layout => false + render layout: false end def create - create!(:notice => I18n.t('stock_takings.create.notice')) + create!(notice: I18n.t('stock_takings.create.notice')) end def update - update!(:notice => I18n.t('stock_takings.update.notice')) + update!(notice: I18n.t('stock_takings.update.notice')) end end diff --git a/app/controllers/stockit_controller.rb b/app/controllers/stockit_controller.rb index c44e3380..6bea7b90 100644 --- a/app/controllers/stockit_controller.rb +++ b/app/controllers/stockit_controller.rb @@ -1,88 +1,121 @@ class StockitController < ApplicationController + before_action :new_empty_article_ratio, only: %i[edit update new create derive copy] + before_action :load_article, + only: %i[index_on_stock_article_create index_on_stock_article_update show edit update show_on_stock_article_update + destroy] + before_action :load_article_units, only: %i[edit update new create derive copy] + before_action :prepare_params_for_create_update, only: %i[create update] + def index - @stock_articles = StockArticle.undeleted.includes(:supplier, :article_category) - .order('suppliers.name, article_categories.name, articles.name') + @stock_articles = StockArticle.with_latest_versions_and_categories.joins(:supplier).order('suppliers.name, article_categories.name, article_versions.name').undeleted end def index_on_stock_article_create # See publish/subscribe design pattern in /doc. - @stock_article = StockArticle.find(params[:id]) - - render :layout => false + render layout: false end def index_on_stock_article_update # See publish/subscribe design pattern in /doc. - @stock_article = StockArticle.find(params[:id]) + render layout: false + end - render :layout => false + def show + @stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC') end # three possibilites to fill a new_stock_article form # (1) start from blank or use params def new @stock_article = StockArticle.new(params[:stock_article]) + @stock_article.latest_article_version = ArticleVersion.new - render :layout => false + render layout: false end # (2) StockArticle as template def copy - @stock_article = StockArticle.find(params[:stock_article_id]).dup + stock_article = StockArticle.find(params[:stock_article_id]) + @stock_article = stock_article.dup + @stock_article.latest_article_version = stock_article.latest_article_version.duplicate_including_article_unit_ratios - render :layout => false + render layout: false end # (3) non-stock Article as template def derive - @stock_article = Article.find(params[:old_article_id]).becomes(StockArticle).dup - - render :layout => false - end + article = Article.find(params[:old_article_id]) + @stock_article = article.becomes(StockArticle).dup + @stock_article.latest_article_version = article.latest_article_version.duplicate_including_article_unit_ratios - def create - @stock_article = StockArticle.new({ quantity: 0 }.merge(params[:stock_article])) - @stock_article.save! - render :layout => false - rescue ActiveRecord::RecordInvalid - render :action => 'new', :layout => false + render layout: false end def edit - @stock_article = StockArticle.find(params[:id]) - - render :layout => false + render layout: false end - def update - @stock_article = StockArticle.find(params[:id]) - @stock_article.update_attributes!(params[:stock_article]) - render :layout => false + def create + StockArticle.transaction do + @stock_article = StockArticle.create(quantity: 0, supplier_id: @supplier_id) + @stock_article.attributes = { latest_article_version_attributes: params[:article_version] } + @stock_article.save + end + render layout: false rescue ActiveRecord::RecordInvalid - render :action => 'edit', :layout => false + render action: 'new', layout: false end - def show - @stock_article = StockArticle.find(params[:id]) - @stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC') + def update + article_version_attributes = params[:article_version] + @stock_article.update_attributes!(supplier_id: @supplier_id, + latest_article_version_attributes: article_version_attributes) + render layout: false + rescue ActiveRecord::RecordInvalid + render action: 'edit', layout: false end def show_on_stock_article_update # See publish/subscribe design pattern in /doc. - @stock_article = StockArticle.find(params[:id]) - - render :layout => false + render layout: false end def destroy - @stock_article = StockArticle.find(params[:id]) @stock_article.mark_as_deleted - render :layout => false - rescue => error - render :partial => "destroy_fail", :layout => false, - :locals => { :fail_msg => I18n.t('errors.general_msg', :msg => error.message) } + render layout: false + rescue StandardError => e + render partial: 'destroy_fail', layout: false, + locals: { fail_msg: I18n.t('errors.general_msg', msg: e.message) } end # TODO: Fix this!! def articles_search @articles = Article.not_in_stock.limit(8).where('name LIKE ?', "%#{params[:term]}%") - render :json => @articles.map(&:name) + render json: @articles.map(&:name) + end + + private + + def load_article + @stock_article = StockArticle.find(params[:id]) + end + + def load_article_units + additional_units = @stock_article&.current_article_units || [] + @article_units = ArticleUnit.as_options(additional_units: additional_units) + @all_units = ArticleUnit.as_hash(additional_units: additional_units) + end + + def new_empty_article_ratio + @empty_article_unit_ratio = ArticleUnitRatio.new + @empty_article_unit_ratio.article_version = @article.latest_article_version unless @article.nil? + @empty_article_unit_ratio.sort = -1 + end + + def prepare_params_for_create_update + @supplier_id = params[:article_version][:article][:supplier_id] + params[:article_version].delete :article + + # Stock article form doesn't have some of the unit fields -> set them to supplier_order_unit: + params[:article_version][:group_order_unit] = params[:article_version][:supplier_order_unit] + params[:article_version][:billing_unit] = params[:article_version][:supplier_order_unit] + params[:article_version][:price_unit] = params[:article_version][:supplier_order_unit] end end diff --git a/app/controllers/styles_controller.rb b/app/controllers/styles_controller.rb index 5636ec03..6d3a9fd1 100644 --- a/app/controllers/styles_controller.rb +++ b/app/controllers/styles_controller.rb @@ -9,7 +9,7 @@ class StylesController < ApplicationController def foodcoop css = FoodsoftConfig[:custom_css] if css.blank? - render body: nil, content_type: 'text/css', status: 404 + render body: nil, content_type: 'text/css', status: :not_found else expires_in 1.week, public: true if params[:md5].present? render body: css, content_type: 'text/css' diff --git a/app/controllers/supplier_shares_controller.rb b/app/controllers/supplier_shares_controller.rb new file mode 100644 index 00000000..cac31d41 --- /dev/null +++ b/app/controllers/supplier_shares_controller.rb @@ -0,0 +1,15 @@ +class SupplierSharesController < ApplicationController + def create + @supplier = Supplier.find(params[:supplier_id]) + @supplier.update_attribute(:external_uuid, SecureRandom.uuid) + + render 'update' + end + + def destroy + @supplier = Supplier.find(params[:supplier_id]) + @supplier.update_attribute(:external_uuid, nil) + + render 'update' + end +end diff --git a/app/controllers/suppliers_controller.rb b/app/controllers/suppliers_controller.rb index 058c4fbc..56ddf224 100644 --- a/app/controllers/suppliers_controller.rb +++ b/app/controllers/suppliers_controller.rb @@ -1,5 +1,7 @@ +require 'ostruct' + class SuppliersController < ApplicationController - before_action :authenticate_suppliers, :except => [:index, :list] + before_action :authenticate_suppliers, except: %i[index list] helper :deliveries def index @@ -14,14 +16,12 @@ def show end # new supplier - # if shared_supplier_id is given, the new supplier will filled whith its attributes def new - if params[:shared_supplier_id] - shared_supplier = SharedSupplier.find(params[:shared_supplier_id]) - @supplier = shared_supplier.suppliers.new(shared_supplier.autofill_attributes) - else - @supplier = Supplier.new - end + @supplier = Supplier.new + end + + def edit + @supplier = Supplier.find(params[:id]) end def create @@ -31,21 +31,17 @@ def create flash[:notice] = I18n.t('suppliers.create.notice') redirect_to suppliers_path else - render :action => 'new' + render action: 'new' end end - def edit - @supplier = Supplier.find(params[:id]) - end - def update @supplier = Supplier.find(params[:id]) if @supplier.update_attributes(supplier_params) flash[:notice] = I18n.t('suppliers.update.notice') redirect_to @supplier else - render :action => 'edit' + render action: 'edit' end end @@ -54,23 +50,34 @@ def destroy @supplier.mark_as_deleted flash[:notice] = I18n.t('suppliers.destroy.notice') redirect_to suppliers_path - rescue => e - flash[:error] = I18n.t('errors.general_msg', :msg => e.message) + rescue StandardError => e + flash[:error] = I18n.t('errors.general_msg', msg: e.message) redirect_to @supplier end - # gives a list with all available shared_suppliers - def shared_suppliers - @shared_suppliers = SharedSupplier.all + def remote_articles + @supplier = Supplier.find(remote_articles_params.fetch(:supplier_id)) + search_params = {} + search_params[:name] = params.fetch(:name).split if params.include?(:name) + search_params[:origin] = params.fetch(:origin) if params.include?(:origin) + search_params[:page] = params.fetch(:page, 1) + search_params[:per_page] = @per_page + data = @supplier.read_from_remote(search_params) + @articles = data[:articles] + @pagination = OpenStruct.new(data[:pagination]) end private + def remote_articles_params + params.permit(:supplier_id, :name, :origin) + end + def supplier_params params .require(:supplier) .permit(:name, :address, :phone, :phone2, :fax, :email, :url, :contact_person, :customer_number, :iban, :custom_fields, :delivery_days, :order_howto, :note, :supplier_category_id, - :shared_supplier_id, :min_order_quantity, :shared_sync_method) + :min_order_quantity, :shared_sync_method, :supplier_remote_source) end end diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index db4ca1ab..352c71ae 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -11,35 +11,33 @@ def user @accepted_tasks = Task.accepted_tasks_for(current_user) end + def show + @task = Task.find(params[:id]) + end + def new @task = Task.new(current_user_id: current_user.id) end + def edit + @task = Task.find(params[:id]) + @periodic = !!params[:periodic] + @task.current_user_id = current_user.id + end + def create @task = Task.new(current_user_id: current_user.id) @task.created_by = current_user @task.attributes = (task_params) - if params[:periodic] - @task.periodic_task_group = PeriodicTaskGroup.new - end + @task.periodic_task_group = PeriodicTaskGroup.new if params[:periodic] if @task.save @task.periodic_task_group.create_tasks_for_upfront_days if params[:periodic] - redirect_to tasks_url, :notice => I18n.t('tasks.create.notice') + redirect_to tasks_url, notice: I18n.t('tasks.create.notice') else - render :template => "tasks/new" + render template: 'tasks/new' end end - def show - @task = Task.find(params[:id]) - end - - def edit - @task = Task.find(params[:id]) - @periodic = !!params[:periodic] - @task.current_user_id = current_user.id - end - def update @task = Task.find(params[:id]) task_group = @task.periodic_task_group @@ -50,16 +48,14 @@ def update if @task.errors.empty? && @task.save task_group.update_tasks_including(@task, prev_due_date) if params[:periodic] flash[:notice] = I18n.t('tasks.update.notice') - if was_periodic && !@task.periodic? - flash[:notice] = I18n.t('tasks.update.notice_converted') - end + flash[:notice] = I18n.t('tasks.update.notice_converted') if was_periodic && !@task.periodic? if @task.workgroup redirect_to workgroup_tasks_url(workgroup_id: @task.workgroup_id) else redirect_to tasks_url end else - render :template => "tasks/edit" + render template: 'tasks/edit' end end @@ -75,7 +71,7 @@ def destroy end task.update_ordergroup_stats(user_ids) - redirect_to tasks_url, :notice => I18n.t('tasks.destroy.notice') + redirect_to tasks_url, notice: I18n.t('tasks.destroy.notice') end # assign current_user to the task and set the assignment to "accepted" @@ -85,20 +81,20 @@ def accept if ass = task.is_assigned?(current_user) ass.update_attribute(:accepted, true) else - task.assignments.create(:user => current_user, :accepted => true) + task.assignments.create(user: current_user, accepted: true) end - redirect_to user_tasks_path, :notice => I18n.t('tasks.accept.notice') + redirect_to user_tasks_path, notice: I18n.t('tasks.accept.notice') end # deletes assignment between current_user and given taskcurrent_user_id: current_user.id def reject Task.find(params[:id]).users.delete(current_user) - redirect_to :action => "index" + redirect_to action: 'index' end def set_done Task.find(params[:id]).update_attribute :done, true - redirect_to tasks_url, :notice => I18n.t('tasks.set_done.notice') + redirect_to tasks_url, notice: I18n.t('tasks.set_done.notice') end # Shows all tasks, which are already done @@ -109,9 +105,9 @@ def archive # shows workgroup (normal group) to edit weekly_tasks_template def workgroup @group = Group.find(params[:workgroup_id]) - if @group.is_a? Ordergroup - redirect_to tasks_url, :alert => I18n.t('tasks.error_not_found') - end + return unless @group.is_a? Ordergroup + + redirect_to tasks_url, alert: I18n.t('tasks.error_not_found') end private diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 503bc79b..df56ade0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,7 +3,7 @@ class UsersController < ApplicationController def index @users = User.undeleted.natural_search(params[:q]) respond_to do |format| - format.json { render :json => @users.map(&:token_attributes).to_json } + format.json { render json: @users.map(&:token_attributes).to_json } end end end diff --git a/app/documents/order_by_articles.rb b/app/documents/order_by_articles.rb index 84fb5c00..bb8bebcc 100644 --- a/app/documents/order_by_articles.rb +++ b/app/documents/order_by_articles.rb @@ -1,15 +1,16 @@ class OrderByArticles < OrderPdf def filename - I18n.t('documents.order_by_articles.filename', :name => order.name, :date => order.ends.to_date) + '.pdf' + I18n.t('documents.order_by_articles.filename', name: order.name, date: order.ends.to_date) + '.pdf' end def title - I18n.t('documents.order_by_articles.title', :name => order.name, - :date => order.ends.strftime(I18n.t('date.formats.default'))) + I18n.t('documents.order_by_articles.title', name: order.name, + date: order.ends.strftime(I18n.t('date.formats.default'))) end def body each_order_article do |order_article| + article_version = order_article.article_version dimrows = [] rows = [[ GroupOrder.human_attribute_name(:ordergroup), @@ -21,13 +22,16 @@ def body each_group_order_article_for_order_article(order_article) do |goa| dimrows << rows.length if goa.result == 0 rows << [goa.group_order.ordergroup_name, - group_order_article_quantity_with_tolerance(goa), - goa.result, + billign_quantity_with_tolerance(goa), + article_version.convert_quantity(goa.result, article_version.group_order_unit, + article_version.billing_unit), number_to_currency(goa.total_price)] end next unless rows.length > 1 - name = "#{order_article.article.name} (#{order_article.article.unit} | #{order_article.price.unit_quantity} | #{number_to_currency(order_article.price.fc_price)})" + name = "#{article_version.name}, #{format_billing_unit_with_ratios(article_version)}, #{number_to_currency(article_version.convert_quantity( + article_version.fc_price, article_version.billing_unit, article_version.supplier_order_unit + ))}" name += " #{order_article.order.name}" if @options[:show_supplier] nice_table name, rows, dimrows do |table| table.column(0).width = bounds.width / 2 diff --git a/app/documents/order_by_groups.rb b/app/documents/order_by_groups.rb index d6711731..6d91e98c 100644 --- a/app/documents/order_by_groups.rb +++ b/app/documents/order_by_groups.rb @@ -1,11 +1,11 @@ class OrderByGroups < OrderPdf def filename - I18n.t('documents.order_by_groups.filename', :name => order.name, :date => order.ends.to_date) + '.pdf' + I18n.t('documents.order_by_groups.filename', name: order.name, date: order.ends.to_date) + '.pdf' end def title - I18n.t('documents.order_by_groups.title', :name => order.name, - :date => order.ends.strftime(I18n.t('date.formats.default'))) + I18n.t('documents.order_by_groups.title', name: order.name, + date: order.ends.strftime(I18n.t('date.formats.default'))) end def body @@ -23,11 +23,11 @@ def body each_group_order_article_for_ordergroup(oa_id) do |goa| dimrows << rows.length if goa.result == 0 - rows << [goa.order_article.article.name, + rows << [goa.order_article.article_version.name, goa.group_order.order.name, - group_order_article_quantity_with_tolerance(goa), - group_order_article_result(goa), - order_article_price_per_unit(goa.order_article), + billign_quantity_with_tolerance(goa), + billing_article_result(goa), + price_per_billing_unit(goa), number_to_currency(goa.total_price)] end next unless rows.length > 1 diff --git a/app/documents/order_fax.rb b/app/documents/order_fax.rb index b4b50577..4a1f741c 100644 --- a/app/documents/order_fax.rb +++ b/app/documents/order_fax.rb @@ -1,8 +1,10 @@ class OrderFax < OrderPdf + include ArticlesHelper + BATCH_SIZE = 250 def filename - I18n.t('documents.order_fax.filename', :name => order.name, :date => order.ends.to_date) + '.pdf' + I18n.t('documents.order_fax.filename', name: order.name, date: order.ends.to_date) + '.pdf' end def title @@ -10,9 +12,20 @@ def title end def body - contact = FoodsoftConfig[:contact].symbolize_keys + from_paragraph + + recipient_paragraph + + articles_paragraph + rescue StandardError => e + Rails.logger.info e.backtrace + raise # always reraise + end + + private - # From paragraph + def from_paragraph + contact = FoodsoftConfig[:contact].symbolize_keys bounding_box [margin_box.right - 200, margin_box.top], width: 200 do text FoodsoftConfig[:name], size: fontsize(9), align: :right move_down 5 @@ -20,25 +33,28 @@ def body move_down 5 text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :right move_down 5 - unless order.supplier.try(:customer_number).blank? - text "#{Supplier.human_attribute_name :customer_number}: #{order.supplier[:customer_number]}", size: fontsize(9), align: :right + if order.supplier.try(:customer_number).present? + text "#{Supplier.human_attribute_name :customer_number}: #{order.supplier[:customer_number]}", + size: fontsize(9), align: :right move_down 5 end - unless contact[:phone].blank? + if contact[:phone].present? text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :right move_down 5 end - unless contact[:email].blank? - text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :right + if contact[:email].present? + text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), + align: :right end end + end - # Recipient + def recipient_paragraph bounding_box [margin_box.left, margin_box.top - 60], width: 200 do text order.name move_down 5 text order.supplier.try(:address).to_s - unless order.supplier.try(:fax).blank? + if order.supplier.try(:fax).present? move_down 5 text "#{Supplier.human_attribute_name :fax}: #{order.supplier[:fax]}" end @@ -50,23 +66,27 @@ def body move_down 10 text "#{Delivery.human_attribute_name :date}:" move_down 10 - unless order.supplier.try(:contact_person).blank? - text "#{Supplier.human_attribute_name :contact_person}: #{order.supplier[:contact_person]}" - move_down 10 - end + return if order.supplier.try(:contact_person).blank? - # Articles + text "#{Supplier.human_attribute_name :contact_person}: #{order.supplier[:contact_person]}" + move_down 10 + end + + def articles_paragraph total = 0 data = [I18n.t('documents.order_fax.rows')] each_order_article do |oa| - subtotal = oa.units_to_order * oa.price.unit_quantity * oa.price.price + price = oa.article_version.price + subtotal = oa.units_to_order * price total += subtotal - data << [oa.article.order_number, + data << [oa.article_version.order_number, oa.units_to_order, - oa.article.name, - oa.price.unit_quantity, - oa.article.unit, - number_to_currency(oa.price.price), + oa.article_version.name, + # TODO-article-units: Why should we show the supplier the group order unit quantity?: + oa.article_version.convert_quantity(1, oa.article_version.supplier_order_unit, + oa.article_version.group_order_unit), + format_supplier_order_unit_with_ratios(oa.price), + number_to_currency(price), number_to_currency(subtotal)] end data << [I18n.t('documents.order_fax.total'), nil, nil, nil, nil, nil, number_to_currency(total)] @@ -78,8 +98,8 @@ def body table.row(0).border_bottom_width = 2 table.columns(1).align = :right table.columns(3..6).align = :right - table.row(data.length - 1).columns(0..5).borders = [:top, :bottom] - table.row(data.length - 1).columns(0).borders = [:top, :bottom, :left] + table.row(data.length - 1).columns(0..5).borders = %i[top bottom] + table.row(data.length - 1).columns(0).borders = %i[top bottom left] table.row(data.length - 1).border_top_width = 2 end # font_size: fontsize(8), @@ -89,16 +109,14 @@ def body # align: {0 => :left} end - private - def order_articles order.order_articles.ordered - .joins(:article) - .order('articles.order_number').order('articles.name') - .preload(:article, :article_price) + .joins(:article_version) + .order('article_versions.order_number').order('article_versions.name') + .preload(article_version: :article) end - def each_order_article - order_articles.find_each_with_order(batch_size: BATCH_SIZE) { |oa| yield oa } + def each_order_article(&block) + order_articles.find_each_with_order(batch_size: BATCH_SIZE, &block) end end diff --git a/app/documents/order_matrix.rb b/app/documents/order_matrix.rb index 7269feaf..9676b760 100644 --- a/app/documents/order_matrix.rb +++ b/app/documents/order_matrix.rb @@ -3,29 +3,29 @@ class OrderMatrix < OrderPdf PLACEHOLDER_CHAR = 'X' def filename - I18n.t('documents.order_matrix.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf' + I18n.t('documents.order_matrix.filename', name: @order.name, date: @order.ends.to_date) + '.pdf' end def title - I18n.t('documents.order_matrix.title', :name => @order.name, - :date => @order.ends.strftime(I18n.t('date.formats.default'))) + I18n.t('documents.order_matrix.title', name: @order.name, + date: @order.ends.strftime(I18n.t('date.formats.default'))) end def body order_articles_data = [[ OrderArticle.human_attribute_name(:article), Article.human_attribute_name(:supplier), - ArticlePrice.human_attribute_name(:unit_quantity), + ArticleVersion.human_attribute_name(:unit_quantity), OrderArticle.human_attribute_name(:units_received), Article.human_attribute_name(:fc_price_short) ]] - each_order_article do |a| - order_articles_data << [a.article.name, - a.article.supplier.name, - a.price.unit_quantity, - a.units, - order_article_price_per_unit(a)] + each_order_article do |oa| + order_articles_data << [oa.article_version.name, + oa.article_version.article.supplier.name, + oa.article_version.unit_quantity, + oa.units, + order_article_price_per_unit(oa)] end order_articles_data.each { |row| row.delete_at 1 } unless @options[:show_supplier] @@ -68,7 +68,7 @@ def body last_supplier_id = -1 each_order_article do |order_article| - supplier = order_article.article.supplier + supplier = order_article.article_version.article.supplier if @options[:show_supplier] && last_supplier_id != supplier.id row = [make_cell(supplier.name, colspan: 2, font_style: :bold)] batch_groups.each { row << nil } @@ -76,7 +76,7 @@ def body last_supplier_id = supplier.id end - row = [order_article.article.name, order_article_price_per_unit(order_article)] + row = [order_article.article_version.name, order_article_price_per_unit(order_article)] row += batch_results[order_article.id] if batch_results[order_article.id] rows << row end @@ -87,7 +87,7 @@ def body table.cells.border_width = 0.5 table.cells.border_color = '666666' - table.row(0).borders = [:bottom, :left] + table.row(0).borders = %i[bottom left] table.row(0).padding = [2, 0, 2, 0] table.row(1..-1).height = row_height_1 table.column(0..1).borders = [] @@ -106,7 +106,7 @@ def body table.column(2 + idx).border_width = 2 end - table.row_colors = ['dddddd', 'ffffff'] + table.row_colors = %w[dddddd ffffff] end first_page = false diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index 0185a0df..3c1da9f0 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -28,7 +28,11 @@ def config_input(form, key, options = {}, &block) options[:default] = options[:input_html].delete(:value) return form.input key, options, &block end - block ||= proc { config_input_field form, key, options.merge(options[:input_html]) } if options[:as] == :select_recurring + if options[:as] == :select_recurring + block ||= proc { + config_input_field form, key, options.merge(options[:input_html]) + } + end form.input key, options, &block end @@ -57,11 +61,12 @@ def config_input_field(form, key, options = {}) unchecked_value = options.delete(:unchecked_value) || 'false' options[:checked] = 'checked' if v = options.delete(:value) && v != 'false' # different key for hidden field so that allow clocking on label focuses the control - form.hidden_field(key, id: "#{key}_", value: unchecked_value, as: :hidden) + form.check_box(key, options, checked_value, false) + form.hidden_field(key, id: "#{key}_", value: unchecked_value, + as: :hidden) + form.check_box(key, options, checked_value, false) elsif options[:as] == :select_recurring options[:value] = FoodsoftDateUtil.rule_from(options[:value]) options[:rules] ||= [] - options[:rules].unshift options[:value] unless options[:value].blank? + options[:rules].unshift options[:value] if options[:value].present? options[:rules].push [I18n.t('recurring_select.not_recurring'), '{}'] if options.delete(:allow_blank) # blank after current value form.select_recurring key, options.delete(:rules).uniq, options else @@ -73,7 +78,7 @@ def config_input_field(form, key, options = {}) # @param form [ActionView::Helpers::FormBuilder] Form object. # @param key [Symbol, String] Configuration key of a boolean (e.g. +use_messages+). # @option options [String] :label Label to show - def config_use_heading(form, key, options = {}) + def config_use_heading(form, key, options = {}, &block) head = content_tag :label do lbl = options[:label] || config_input_label(form, key) field = config_input_field(form, key, as: :boolean, boolean_style: :inline, @@ -83,9 +88,7 @@ def config_use_heading(form, key, options = {}) content_tag :span, (lbl + field).html_safe, config_input_tooltip_options(form, key, {}) end end - fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}") do - yield - end + fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}", &block) head + fields end @@ -127,7 +130,7 @@ def config_input_tooltip_options(form, key, options) # tooltip with help info to the right cfg_path = form.lookup_model_names[1..-1] + [key] tooltip = I18n.t("config.hints.#{cfg_path.map(&:to_s).join('.')}", default: '') - unless tooltip.blank? + if tooltip.present? options[:data] ||= {} options[:data][:toggle] ||= 'tooltip' options[:data][:placement] ||= 'right' diff --git a/app/helpers/admin/ordergroups_helper.rb b/app/helpers/admin/ordergroups_helper.rb index e74fdde5..ecb4bd39 100644 --- a/app/helpers/admin/ordergroups_helper.rb +++ b/app/helpers/admin/ordergroups_helper.rb @@ -2,9 +2,7 @@ module Admin::OrdergroupsHelper def ordergroup_members_title(ordergroup) s = '' s += ordergroup.users.map(&:name).join(', ') if ordergroup.users.any? - if ordergroup.contact_person.present? - s += "\n" + Ordergroup.human_attribute_name(:contact) + ": " + ordergroup.contact_person - end + s += "\n" + Ordergroup.human_attribute_name(:contact) + ': ' + ordergroup.contact_person if ordergroup.contact_person.present? s end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de207901..5412e1bc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -4,7 +4,7 @@ module ApplicationHelper include PathHelper def format_time(time = Time.now) - I18n.l(time, :format => "%d.%m.%Y %H:%M") unless time.nil? + I18n.l(time, format: '%d.%m.%Y %H:%M') unless time.nil? end def format_date(time = Time.now) @@ -16,7 +16,7 @@ def format_datetime(time = Time.now) end def format_datetime_timespec(time, format) - I18n.l(time, :format => format) unless (time.nil? || format.nil?) + I18n.l(time, format: format) unless time.nil? || format.nil? end def format_currency(amount) @@ -26,28 +26,28 @@ def format_currency(amount) # Splits an IBAN into groups of 4 digits displayed with margins in between def format_iban(iban) - iban && iban.scan(/..?.?.?/).map { |item| content_tag(:span, item, style: "margin-right: 0.5em;") }.join.html_safe + iban && iban.scan(/..?.?.?/).map { |item| content_tag(:span, item, style: 'margin-right: 0.5em;') }.join.html_safe end # Creates ajax-controlled-links for pagination def pagination_links_remote(collection, options = {}) per_page = options[:per_page] || @per_page params = options[:params] || {} - params = params.merge({ :per_page => per_page }) - paginate collection, :params => params, :remote => true + params = params.merge({ per_page: per_page }) + paginate collection, params: params, remote: true end # Link-collection for per_page-options when using the pagination-plugin def items_per_page(options = {}) per_page_options = options[:per_page_options] || [20, 50, 100, 500] current = options[:current] || @per_page - params = params || {} + params ||= {} links = per_page_options.map do |per_page| - params.merge!({ :per_page => per_page }) + params.merge!({ per_page: per_page }) link_class = 'btn' link_class << ' disabled' if per_page == current - link_to(per_page, params, :remote => true, class: link_class) + link_to(per_page, params, remote: true, class: link_class) end if options[:wrap] == false @@ -63,21 +63,19 @@ def sort_link_helper(text, key, options = {}) # Hmtl options remote = options[:remote].nil? ? true : options[:remote] class_name = case params[:sort] - when key then + when key 'sortup' - when key + '_reverse' then + when key + '_reverse' 'sortdown' - else - nil end html_options = { - :title => I18n.t('helpers.application.sort_by', text: text), - :remote => remote, - :class => class_name + title: I18n.t('helpers.application.sort_by', text: text), + remote: remote, + class: class_name } # Url options - key += "_reverse" if params[:sort] == key + key += '_reverse' if params[:sort] == key per_page = options[:per_page] || @per_page url_options = params.merge(per_page: per_page, sort: key) url_options.merge!({ page: params[:page] }) if params[:page] @@ -95,14 +93,16 @@ def sort_link_helper(text, key, options = {}) # be overridden by the option 'desc'. # Other options are passed through to I18n. def heading_helper(model, attribute, options = {}) - i18nopts = { count: 2 }.merge(options.select { |a| !['short', 'desc'].include?(a) }) + i18nopts = { count: 2 }.merge(options.select { |a| !%w[short desc].include?(a) }) s = model.human_attribute_name(attribute, i18nopts) if options[:short] desc = options[:desc] - desc ||= model.human_attribute_name("#{attribute}_desc".to_sym, options.merge({ fallback: true, default: '', count: 2 })) + desc ||= model.human_attribute_name(:"#{attribute}_desc", + options.merge({ fallback: true, default: '', count: 2 })) desc.blank? && desc = s - sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({ fallback: true, default: '', count: 2 })) - s = raw "#{sshort}" unless sshort.blank? + sshort = model.human_attribute_name(:"#{attribute}_short", + options.merge({ fallback: true, default: '', count: 2 })) + s = raw "#{sshort}" if sshort.present? end s end @@ -117,7 +117,7 @@ def link_to_top # Returns the weekday. 0 is sunday, 1 is monday and so on def weekday(dayNumber) weekdays = I18n.t('date.day_names') - return weekdays[dayNumber] + weekdays[dayNumber] end # to set a title for both the h1-tag and the title in the header @@ -136,13 +136,13 @@ def tab_is_active?(tab) def icon(name, options = {}) icons = { - :delete => { :file => 'b_drop.png', :alt => I18n.t('ui.delete') }, - :edit => { :file => 'b_edit.png', :alt => I18n.t('ui.edit') }, - :members => { :file => 'b_users.png', :alt => I18n.t('helpers.application.edit_user') } + delete: { file: 'b_drop.png', alt: I18n.t('ui.delete') }, + edit: { file: 'b_edit.png', alt: I18n.t('ui.edit') }, + members: { file: 'b_users.png', alt: I18n.t('helpers.application.edit_user') } } options[:alt] ||= icons[name][:alt] options[:title] ||= icons[name][:title] - options.merge!({ :size => '16x16', :border => "0" }) + options.merge!({ size: '16x16', border: '0' }) image_tag icons[name][:file], options end @@ -150,27 +150,29 @@ def icon(name, options = {}) # Remote links with default 'loader'.gif during request def remote_link_to(text, options = {}) remote_options = { - :before => "Element.show('loader')", - :success => "Element.hide('loader')", - :method => :get + before: "Element.show('loader')", + success: "Element.hide('loader')", + method: :get } link_to(text, options[:url], remote_options.merge(options)) end def format_roles(record, icon = false) - roles = %w(suppliers article_meta orders pickups finance invoices admin) + roles = %w[suppliers article_meta orders pickups finance invoices admin] roles.select! { |role| record.send "role_#{role}?" } - names = Hash[roles.map { |r| [r, I18n.t("helpers.application.role_#{r}")] }] + names = roles.index_with { |r| I18n.t("helpers.application.role_#{r}") } if icon - roles.map { |r| image_tag("role-#{r}.png", size: '22x22', border: 0, alt: names[r], title: names[r]) }.join(' ').html_safe + roles.map do |r| + image_tag("role-#{r}.png", size: '22x22', border: 0, alt: names[r], title: names[r]) + end.join(' ').html_safe else roles.map { |r| names[r] }.join(', ') end end def link_to_gmaps(address) - link_to h(address), "http://maps.google.com/?q=#{h(address)}", :title => I18n.t('helpers.application.show_google_maps'), - :target => "_blank" + link_to h(address), "http://maps.google.com/?q=#{h(address)}", title: I18n.t('helpers.application.show_google_maps'), + target: '_blank', rel: 'noopener' end # Returns flash messages html. @@ -186,8 +188,8 @@ def bootstrap_flash_patched type = :success if type == 'notice' type = :error if type == 'alert' text = content_tag(:div, - content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => "close", "data-dismiss" => "alert") + - message, :class => "alert fade in alert-#{type}") + content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => 'close', 'data-dismiss' => 'alert') + + message, class: "alert fade in alert-#{type}") flash_messages << text if message end flash_messages.join("\n").html_safe @@ -195,17 +197,17 @@ def bootstrap_flash_patched # render base errors in a form after failed validation # http://railsapps.github.io/twitter-bootstrap-rails.html - def base_errors resource + def base_errors(resource) return '' if resource.errors.empty? || resource.errors[:base].empty? messages = resource.errors[:base].map { |msg| content_tag(:li, msg) }.join - render :partial => 'shared/base_errors', :locals => { :error_messages => messages } + render partial: 'shared/base_errors', locals: { error_messages: messages } end # show a user, depending on settings def show_user(user = @current_user, options = {}) if user.nil? - "?" + '?' elsif FoodsoftConfig[:use_nick] if options[:full] && options[:markup] raw "#{h user.nick} (#{h user.first_name} #{h user.last_name})" @@ -216,7 +218,7 @@ def show_user(user = @current_user, options = {}) user.nick.nil? ? I18n.t('helpers.application.nick_fallback') : user.nick end else - "#{user.first_name} #{user.last_name}" + (options[:unique] ? " (\##{user.id})" : '') + "#{user.first_name} #{user.last_name}" + (options[:unique] ? " (##{user.id})" : '') end end @@ -258,9 +260,13 @@ def foodcoop_css_path(options = {}) # @return [String] stylesheet tag for foodcoop CSS style (+custom_css+ foodcoop config) # @see #foodcoop_css_path - def foodcoop_css_tag(options = {}) - unless FoodsoftConfig[:custom_css].blank? - stylesheet_link_tag foodcoop_css_path, media: 'all' - end + def foodcoop_css_tag(_options = {}) + return if FoodsoftConfig[:custom_css].blank? + + stylesheet_link_tag foodcoop_css_path, media: 'all' + end + + def format_number(value) + format('%g', format('%.2f', value)) end end diff --git a/app/helpers/articles_helper.rb b/app/helpers/articles_helper.rb index add1c6ba..45781967 100644 --- a/app/helpers/articles_helper.rb +++ b/app/helpers/articles_helper.rb @@ -1,15 +1,81 @@ module ArticlesHelper # useful for highlighting attributes, when synchronizing articles - def highlight_new(unequal_attributes, attribute) + def highlight_new(unequal_attributes, attributes) + attributes = [attributes] unless attributes.is_a?(Array) return unless unequal_attributes - unequal_attributes.has_key?(attribute) ? "background-color: yellow" : "" + intersection = (unequal_attributes.keys & attributes) + intersection.empty? ? '' : 'background-color: yellow' end def row_classes(article) classes = [] - classes << "unavailable" if !article.availability - classes << "just-updated" if article.recently_updated && article.availability - classes.join(" ") + classes << 'unavailable' unless article.availability + classes << 'just-updated' if article.recently_updated && article.availability + classes.join(' ') + end + + def format_unit(unit_property, article) + unit_code = article.send(unit_property) + return article.unit if unit_code.nil? + + unit = ArticleUnitsLib.units.to_h[unit_code] + unit[:symbol] || unit[:name] + end + + def format_supplier_order_unit(article) + format_unit(:supplier_order_unit, article) + end + + def format_group_order_unit(article) + format_unit(:group_order_unit, article) + end + + def format_billing_unit(article) + format_unit(:billing_unit, article) + end + + def format_unit_with_ratios(unit_property, article) + base = format_unit(unit_property, article) + unit_code = article.send(unit_property) + return base if ArticleUnitsLib.unit_is_si_convertible(unit_code) + + relevant_units = [article.article_unit_ratios.map(&:unit)] + relevant_units << article.supplier_order_unit unless unit_property == :supplier_order_unit + first_si_convertible_unit = relevant_units + .flatten + .find { |unit| ArticleUnitsLib.unit_is_si_convertible(unit) } + + return base if first_si_convertible_unit.nil? + + quantity = article.convert_quantity(1, unit_code, first_si_convertible_unit) + "#{base} (#{format_number(quantity)}\u00a0#{ArticleUnitsLib.units.to_h[first_si_convertible_unit][:symbol]})" + end + + def format_supplier_order_unit_with_ratios(article) + format_unit_with_ratios(:supplier_order_unit, article) + end + + def format_group_order_unit_with_ratios(article) + format_unit_with_ratios(:group_order_unit, article) + end + + def format_billing_unit_with_ratios(article) + format_unit_with_ratios(:billing_unit, article) + end + + def field_with_preset_value_and_errors(options) + form, field, value, field_errors, input_html = options.values_at(:form, :field, :value, :errors, :input_html) + form.input field, label: false, wrapper_html: { class: field_errors.blank? ? '' : 'error' }, + input_html: input_html do + output = [form.input_field(field, { value: value }.merge(input_html))] + if field_errors.present? + errors = tag.span(class: 'help-inline') do + field_errors.join(', ') + end + output << errors + end + safe_join(output) + end end end diff --git a/app/helpers/deliveries_helper.rb b/app/helpers/deliveries_helper.rb index a97a7df7..6d4aa0f1 100644 --- a/app/helpers/deliveries_helper.rb +++ b/app/helpers/deliveries_helper.rb @@ -10,23 +10,27 @@ def link_to_invoice(delivery) end def articles_for_select2(articles, except = [], &block) - articles = articles.reorder('articles.name ASC') - articles = articles.reject { |a| not except.index(a.id).nil? } if except - block_given? or block = Proc.new { |a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" } + articles = articles.reorder('article_versions.name ASC') + articles = articles.reject { |a| !except.index(a.id).nil? } if except + block_given? or block = proc { |a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" } articles.map do |a| - { :id => a.id, :text => block.call(a) } - end.unshift({ :id => '', :text => '' }) + { id: a.id, text: block.call(a) } + end.unshift({ id: '', text: '' }) end def articles_for_table(articles) - articles.undeleted.reorder('articles.name ASC') + articles.undeleted.reorder('article_versions.name ASC') end def stock_change_remove_link(stock_change_form) - return link_to t('deliveries.stock_change_fields.remove_article'), "#", :class => 'remove_new_stock_change btn btn-small' if stock_change_form.object.new_record? + if stock_change_form.object.new_record? + return link_to t('deliveries.stock_change_fields.remove_article'), '#', + class: 'remove_new_stock_change btn btn-small' + end output = stock_change_form.hidden_field :_destroy - output += link_to t('deliveries.stock_change_fields.remove_article'), "#", :class => 'destroy_stock_change btn btn-small' - return output.html_safe + output += link_to t('deliveries.stock_change_fields.remove_article'), '#', + class: 'destroy_stock_change btn btn-small' + output.html_safe end end diff --git a/app/helpers/finance/balancing_helper.rb b/app/helpers/finance/balancing_helper.rb index bc528f04..a123b161 100644 --- a/app/helpers/finance/balancing_helper.rb +++ b/app/helpers/finance/balancing_helper.rb @@ -2,11 +2,11 @@ module Finance::BalancingHelper def balancing_view_partial view = params[:view] || 'edit_results' case view - when 'edit_results' then + when 'edit_results' 'edit_results_by_articles' - when 'groups_overview' then + when 'groups_overview' 'shared/articles_by/groups' - when 'articles_overview' then + when 'articles_overview' 'shared/articles_by/articles' end end diff --git a/app/helpers/finance/invoices_helper.rb b/app/helpers/finance/invoices_helper.rb index ef01a275..0644b501 100644 --- a/app/helpers/finance/invoices_helper.rb +++ b/app/helpers/finance/invoices_helper.rb @@ -1,9 +1,9 @@ module Finance::InvoicesHelper - def format_delivery_item delivery + def format_delivery_item(delivery) format_date(delivery.date) end - def format_order_item order + def format_order_item(order) "#{format_date(order.ends)} (#{number_to_currency(order.sum)})" end end diff --git a/app/helpers/group_order_articles_helper.rb b/app/helpers/group_order_articles_helper.rb index ff003731..db2fd087 100644 --- a/app/helpers/group_order_articles_helper.rb +++ b/app/helpers/group_order_articles_helper.rb @@ -1,13 +1,28 @@ module GroupOrderArticlesHelper # return an edit field for a GroupOrderArticle result - def group_order_article_edit_result(goa) + def group_order_article_edit_result(goa, convert_to_billing_unit = true) result = number_with_precision goa.result, strip_insignificant_zeros: true - unless goa.group_order.order.finished? && current_user.role_finance? - result - else + if goa.group_order.order.finished? && current_user.role_finance? + order_article = goa.order_article + article_version = order_article.article_version simple_form_for goa, remote: true, html: { 'data-submit-onchange' => 'changed', class: 'delta-input' } do |f| - f.input_field :result, as: :delta, class: 'input-nano', data: { min: 0 }, id: "r_#{goa.id}", value: result + quantity_data = ratio_quantity_data(order_article, order_article.article_version.billing_unit) + converted_value = if convert_to_billing_unit + article_version.convert_quantity(goa.result, + article_version.group_order_unit, article_version.billing_unit) + else + result + end + input_data = { min: 0 }.merge(quantity_data) + if convert_to_billing_unit + input_data = input_data.merge('multiply-before-submit': article_version.convert_quantity(1, + article_version.billing_unit, article_version.group_order_unit)) + end + f.input_field(:result, as: :delta, class: 'input-nano', data: input_data, id: "r_#{goa.id}", + value: converted_value.round(3)) end + else + result end end end diff --git a/app/helpers/group_orders_helper.rb b/app/helpers/group_orders_helper.rb index c5e27c66..438b1230 100644 --- a/app/helpers/group_orders_helper.rb +++ b/app/helpers/group_orders_helper.rb @@ -1,10 +1,11 @@ module GroupOrdersHelper def data_to_js(ordering_data) - ordering_data[:order_articles].map { |id, data| - [id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance], data[:used_quantity], data[:quantity_available]] - }.map { |row| + ordering_data[:order_articles].map do |id, data| + [id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance], + data[:used_quantity], data[:quantity_available]] + end.map do |row| "addData(#{row.join(', ')});" - }.join("\n") + end.join("\n") end # Returns a link to the page where a group_order can be edited. @@ -14,9 +15,9 @@ def link_to_ordering(order, options = {}, &block) path = if options[:show] && group_order group_order_path(group_order) elsif group_order - edit_group_order_path(group_order, :order_id => order.id) + edit_group_order_path(group_order, order_id: order.id) else - new_group_order_path(:order_id => order.id) + new_group_order_path(order_id: order.id) end options.delete(:show) name = block_given? ? capture(&block) : order.name @@ -26,7 +27,7 @@ def link_to_ordering(order, options = {}, &block) # Return css class names for order result table def order_article_class_name(quantity, tolerance, result) - if (quantity + tolerance > 0) + if quantity + tolerance > 0 result > 0 ? 'success' : 'failed' else 'ignored' @@ -44,13 +45,20 @@ def get_order_results(order_article, group_order_id) { group_order_article: goa, quantity: quantity, tolerance: tolerance, result: result, sub_total: sub_total } end - def get_missing_units_css_class(quantity_missing) - if (quantity_missing == 1) - return 'missing-few'; - elsif (quantity_missing == 0) - return '' + def requires_tolerance_input?(order_article, ordering_data) + ( + !order_article.article_version.supplier_order_unit_is_si_convertible && + ordering_data[:order_articles][order_article.id][:ratio_group_order_unit_supplier_unit] != order_article.article_version.group_order_granularity + ) || (order_article.article_version.minimum_order_quantity.presence || 0) > order_article.article_version.group_order_granularity + end + + def get_missing_units_css_class(quantity_missing, article_version) + if quantity_missing == 0 + '' + elsif quantity_missing <= article_version.group_order_granularity + 'missing-few' else - return 'missing-many' + 'missing-many' end end end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb index ff238730..91e08a46 100644 --- a/app/helpers/orders_helper.rb +++ b/app/helpers/orders_helper.rb @@ -18,7 +18,7 @@ def order_pdf(order, document, text, options = {}) def options_for_suppliers_to_select options = [[I18n.t('helpers.orders.option_choose')]] - options += Supplier.map { |s| [s.name, url_for(action: "new", supplier_id: s.id)] } + options += Supplier.map { |s| [s.name, url_for(action: 'new', supplier_id: s.id)] } options += [[I18n.t('helpers.orders.option_stock'), url_for(action: 'new', supplier_id: nil)]] options_for_select(options) end @@ -29,18 +29,44 @@ def units_history_line(order_article, options = {}) nil else units_info = [] - [:units_to_order, :units_billed, :units_received].map do |unit| - if n = order_article.send(unit) - line = n.to_s + ' ' - line += pkg_helper(order_article.price, options) + ' ' unless n == 0 - line += OrderArticle.human_attribute_name("#{unit}_short", count: n) - units_info << line - end + price = order_article.price + %i[units_to_order units_billed units_received].map do |unit| + next unless n = order_article.send(unit) + + converted_quantity = price.convert_quantity(n, price.supplier_order_unit, + price.billing_unit.presence || price.supplier_order_unit) + line = converted_quantity.round(3).to_s + ' ' + line += pkg_helper(price, options) + ' ' unless n == 0 + line += OrderArticle.human_attribute_name("#{unit}_short", count: n) + units_info << line end units_info.join(', ').html_safe end end + def ordered_quantities_different_from_group_orders?(order_article, ordered_mark = '!', billed_mark = '?', + received_mark = '?') + price = order_article.price + group_orders_sum_quantity = order_article.group_orders_sum[:quantity] + if !order_article.units_received.nil? + if price.convert_quantity(order_article.units_received, price.supplier_order_unit, + price.group_order_unit).round(3) == group_orders_sum_quantity + false + else + received_mark + end + elsif !order_article.units_billed.nil? + order_article.units_billed == group_orders_sum_quantity ? false : billed_mark + elsif !order_article.units_to_order.nil? + if price.convert_quantity(order_article.units_to_order, price.supplier_order_unit, + price.group_order_unit).round(3) == group_orders_sum_quantity + false + else + ordered_mark + end + end + end + # @param article [Article] # @option options [String] :icon +false+ to hide the icon # @option options [String] :plain +true+ to not use HTML (implies +icon+=+false+) @@ -48,9 +74,15 @@ def units_history_line(order_article, options = {}) # Sensible in tables with multiple columns. # @return [String] Text showing unit and unit quantity when applicable. def pkg_helper(article, options = {}) - return '' if !article || article.unit_quantity == 1 + if options[:unit].nil? || article.supplier_order_unit == options[:unit] + first_ratio = article&.article_unit_ratios&.first + return '' if first_ratio.nil? || first_ratio.quantity == 1 - uq_text = "× #{article.unit_quantity}" + uq_text = "× #{first_ratio.quantity} #{ArticleUnitsLib.units[first_ratio.unit][:symbol]}" + else + unit = ArticleUnitsLib.units[options[:unit]] + uq_text = unit[:symbol] || unit[:name] + end uq_text = content_tag(:span, uq_text, class: 'hidden-phone') if options[:soft_uq] if options[:plain] uq_text @@ -67,60 +99,77 @@ def pkg_helper(article, options = {}) def pkg_helper_icon(c = nil, options = {}) options = { tag: 'i', class: '' }.merge(options) if c.nil? - c = " ".html_safe - options[:class] += " icon-only" + c = ' '.html_safe + options[:class] += ' icon-only' end content_tag(options[:tag], c, class: "package #{options[:class]}").html_safe end - def article_price_change_hint(order_article, gross = false) - return nil if order_article.article.price == order_article.price.price + def article_version_change_hint(order_article, gross = false) + return nil if order_article.article_version.price == order_article.article_version.price - title = "#{t('helpers.orders.old_price')}: #{number_to_currency order_article.article.price}" - title += " / #{number_to_currency order_article.article.gross_price}" if gross + title = "#{t('helpers.orders.old_price')}: #{number_to_currency order_article.article_version.price}" + title += " / #{number_to_currency order_article.article_version.gross_price}" if gross content_tag(:i, nil, class: 'icon-asterisk', title: j(title)).html_safe end def receive_input_field(form) order_article = form.object - units_expected = (order_article.units_billed || order_article.units_to_order) * - 1.0 * order_article.article.unit_quantity / order_article.article_price.unit_quantity + price = order_article.article_version + quantity = order_article.units_billed || order_article.units_to_order + # units_expected = (order_article.units_billed || order_article.units_to_order) * + # 1.0 * order_article.article_version.unit_quantity / order_article.article_version.unit_quantity + units_expected = price.convert_quantity(quantity, price.supplier_order_unit, price.billing_unit) input_classes = 'input input-nano units_received' - input_classes += ' package' unless order_article.article_price.unit_quantity == 1 + input_classes += ' package' unless price.unit_quantity == 1 || price.supplier_order_unit != price.billing_unit + data = { units_expected: units_expected, billing_unit: price.billing_unit } + data.merge!(ratio_quantity_data(order_article, price.billing_unit)) input_html = form.text_field :units_received, class: input_classes, - data: { 'units-expected' => units_expected }, + data: data, disabled: order_article.result_manually_changed?, autocomplete: 'off' if order_article.result_manually_changed? - input_html = content_tag(:span, class: 'input-prepend intable', title: t('orders.edit_amount.field_locked_title', default: '')) { + input_html = content_tag(:span, class: 'input-prepend intable', + title: t('orders.edit_amount.field_locked_title', default: '')) do button_tag(nil, type: :button, class: 'btn unlocker') { content_tag(:i, nil, class: 'icon icon-unlock') } + input_html - } + end end input_html.html_safe end + def ratio_quantity_data(order_article, default_unit = nil) + data = {} + data['supplier-order-unit'] = order_article.article_version.supplier_order_unit + data['default-unit'] = default_unit + data['custom-unit'] = order_article.article_version.unit + order_article.article_version.article_unit_ratios.all.each_with_index do |ratio, index| + data["ratio-quantity-#{index}"] = ratio.quantity + data["ratio-unit-#{index}"] = ratio.unit + end + + data + end + # @param order [Order] # @return [String] Number of ordergroups participating in order with groups in title. def ordergroup_count(order) group_orders = order.group_orders.includes(:ordergroup) txt = "#{group_orders.count} #{Ordergroup.model_name.human count: group_orders.count}" - if group_orders.count == 0 - return txt - else - desc = group_orders.includes(:ordergroup).map { |g| g.ordergroup_name }.join(', ') - content_tag(:abbr, txt, title: desc).html_safe - end + return txt if group_orders.count == 0 + + desc = group_orders.includes(:ordergroup).map { |g| g.ordergroup_name }.join(', ') + content_tag(:abbr, txt, title: desc).html_safe end # @param order_or_supplier [Order, Supplier] Order or supplier to link to # @return [String] Link to order or supplier, showing its name. def supplier_link(order_or_supplier) - if order_or_supplier.kind_of?(Order) && order_or_supplier.stockit? + if order_or_supplier.is_a?(Order) && order_or_supplier.stockit? link_to(order_or_supplier.name, stock_articles_path).html_safe else link_to(@order.supplier.name, supplier_path(@order.supplier)).html_safe @@ -152,7 +201,8 @@ def receive_button(order, options = {}) if order.stockit? content_tag :div, t('orders.index.action_receive'), class: "btn disabled #{options[:class]}" else - link_to t('orders.index.action_receive'), receive_order_path(order), class: "btn#{' btn-success' unless order.received?} #{options[:class]}" + link_to t('orders.index.action_receive'), receive_order_path(order), + class: "btn#{' btn-success' unless order.received?} #{options[:class]}" end end end diff --git a/app/helpers/stockit_helper.rb b/app/helpers/stockit_helper.rb index a08e8335..9848198d 100644 --- a/app/helpers/stockit_helper.rb +++ b/app/helpers/stockit_helper.rb @@ -1,8 +1,8 @@ module StockitHelper def stock_article_classes(article) class_names = [] - class_names << "unavailable" if article.quantity_available <= 0 - class_names.join(" ") + class_names << 'unavailable' if article.quantity_available <= 0 + class_names.join(' ') end def link_to_stock_change_reason(stock_change) @@ -17,8 +17,8 @@ def link_to_stock_change_reason(stock_change) def stock_article_price_hint(stock_article) t('simple_form.hints.stock_article.edit_stock_article.price', - :stock_article_copy_link => link_to(t('stockit.form.copy_stock_article'), - stock_article_copy_path(stock_article), - :remote => true)) + stock_article_copy_link: link_to(t('stockit.form.copy_stock_article'), + stock_article_copy_path(stock_article), + remote: true)) end end diff --git a/app/helpers/suppliers_helper.rb b/app/helpers/suppliers_helper.rb index 507d2035..c4ddec9b 100644 --- a/app/helpers/suppliers_helper.rb +++ b/app/helpers/suppliers_helper.rb @@ -1,10 +1,8 @@ module SuppliersHelper - def associated_supplier_names(shared_supplier) - "(#{shared_supplier.suppliers.map(&:name).join(', ')})" - end - - def shared_sync_method_collection(shared_supplier) - shared_supplier.shared_sync_methods.map do |m| + def shared_sync_method_collection + # TODO: See if we really need the import limit `shared_supplier_article_sync_limit` + # also see https://github.com/foodcoops/foodsoft/pull/609/files + Supplier.shared_sync_methods.keys.map do |m| [t("suppliers.shared_supplier_methods.#{m}"), m] end end diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index f6f1fa14..4b12f7a8 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -1,16 +1,16 @@ module TasksHelper def task_assignments(task) task.assignments.map do |ass| - content_tag :span, show_user(ass.user), :class => (ass.accepted? ? 'accepted' : 'unaccepted') - end.join(", ").html_safe + content_tag :span, show_user(ass.user), class: (ass.accepted? ? 'accepted' : 'unaccepted') + end.join(', ').html_safe end # generate colored number of still required users def highlighted_required_users(task) - unless task.enough_users_assigned? - content_tag :span, task.still_required_users, class: 'badge badge-important', - title: I18n.t('helpers.tasks.required_users', :count => task.still_required_users) - end + return if task.enough_users_assigned? + + content_tag :span, task.still_required_users, class: 'badge badge-important', + title: I18n.t('helpers.tasks.required_users', count: task.still_required_users) end def task_title(task) diff --git a/app/inputs/delta_input.rb b/app/inputs/delta_input.rb index adc08960..12d6fe4f 100644 --- a/app/inputs/delta_input.rb +++ b/app/inputs/delta_input.rb @@ -6,12 +6,12 @@ def input(wrapper_options) options[:data] ||= {} options[:data][:delta] ||= 1 options[:autocomplete] ||= 'off' - # TODO get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option + # TODO: get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option template.content_tag :div, class: 'delta-input input-prepend input-append' do - delta_button(content_tag(:i, nil, class: 'icon icon-minus'), -1, options) + + delta_button(content_tag(:i, nil, class: 'icon icon-plus'), 1, options) + @builder.text_field(attribute_name, options) + - delta_button(content_tag(:i, nil, class: 'icon icon-plus'), 1, options) + delta_button(content_tag(:i, nil, class: 'icon icon-minus'), -1, options) end end # template.button_tag('−', type: :submit, data: {decrement: options[:id]}, tabindex: -1, class: 'btn') + @@ -21,6 +21,7 @@ def input(wrapper_options) def delta_button(title, direction, options) data = { (direction > 0 ? 'increment' : 'decrement') => options[:id] } delta = direction * options[:data][:delta] - template.button_tag(title, type: :button, name: 'delta', value: delta, data: data, tabindex: -1, class: 'btn') + template.button_tag(title, type: :button, name: 'delta', value: delta, data: data, tabindex: -1, + class: 'btn modify') end end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index 4ff340a0..90c8a062 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -81,7 +81,7 @@ def order_result_supplier(user, order, options = {}) add_order_result_attachments order, options - subject = I18n.t('mailer.order_result_supplier.subject', :name => order.supplier.name) + subject = I18n.t('mailer.order_result_supplier.subject', name: order.supplier.name) subject += " (#{I18n.t('activerecord.attributes.order.pickup')}: #{format_date(order.pickup)})" if order.pickup mail to: order.supplier.email, @@ -122,10 +122,11 @@ def mail(args) if args[:from].is_a? User args[:reply_to] ||= args[:from] - args[:from] = format_address(FoodsoftConfig[:email_sender], I18n.t('mailer.from_via_foodsoft', name: show_user(args[:from]))) + args[:from] = + format_address(FoodsoftConfig[:email_sender], I18n.t('mailer.from_via_foodsoft', name: show_user(args[:from]))) end - [:bcc, :cc, :reply_to, :sender, :to].each do |k| + %i[bcc cc reply_to sender to].each do |k| user = args[k] args[k] = format_address(user.email, show_user(user)) if user.is_a? User end @@ -145,21 +146,21 @@ def mail(args) def self.deliver_now_with_user_locale(user, &block) I18n.with_locale(user.settings['profile']['language']) do - self.deliver_now &block + deliver_now(&block) end end def self.deliver_now_with_default_locale(&block) I18n.with_locale(FoodsoftConfig[:default_locale]) do - self.deliver_now &block + deliver_now(&block) end end def self.deliver_now message = yield message.deliver_now - rescue => error - MailDeliveryStatus.create email: message.to[0], message: error.message + rescue StandardError => e + MailDeliveryStatus.create email: message.to[0], message: e.message end # separate method to allow plugins to mess with the attachments @@ -169,8 +170,7 @@ def add_order_result_attachments(order, options = {}) end # separate method to allow plugins to mess with the text - def additonal_welcome_text(user) - end + def additonal_welcome_text(user); end private diff --git a/app/models/article.rb b/app/models/article.rb index f8c129b6..e6e73190 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -19,89 +19,97 @@ class Article < ApplicationRecord # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements # @!attribute price # @return [Number] Net price - # @see ArticlePrice#price + # @see ArticleVersion#price # @!attribute tax # @return [Number] VAT percentage (10 is 10%). - # @see ArticlePrice#tax + # @see ArticleVersion#tax # @!attribute deposit # @return [Number] Deposit - # @see ArticlePrice#deposit + # @see ArticleVersion#deposit # @!attribute unit_quantity # @return [Number] Number of units in wholesale package (box). - # @see ArticlePrice#unit_quantity + # @see ArticleVersion#unit_quantity # @!attribute order_number # Order number, this can be used by the supplier to identify articles. # This is required when using the shared database functionality. # @return [String] Order number. # @!attribute article_category # @return [ArticleCategory] Category this article is in. - belongs_to :article_category # @!attribute supplier # @return [Supplier] Supplier this article belongs to. belongs_to :supplier - # @!attribute article_prices - # @return [Array] Price history (current price first). - has_many :article_prices, -> { order("created_at DESC") } - # @!attribute order_articles - # @return [Array] Order articles for this article. - has_many :order_articles + # @!attribute article_versions + # @return [Array] Price history (current price first). + has_many :article_versions, -> { order('created_at DESC') } + # @!attribute order # @return [Array] Orders this article appears in. has_many :orders, through: :order_articles - # Replace numeric seperator with database format - localize_input_of :price, :tax, :deposit - # Get rid of unwanted whitespace. {Unit#new} may even bork on whitespace. - normalize_attributes :name, :unit, :note, :manufacturer, :origin, :order_number + has_one :latest_article_version, lambda { + merge(ArticleVersion.latest) + }, foreign_key: :article_id, class_name: :ArticleVersion scope :undeleted, -> { where(deleted_at: nil) } - scope :available, -> { undeleted.where(availability: true) } + scope :available, -> { undeleted.with_latest_versions_and_categories.where(article_versions: { availability: true }) } scope :not_in_stock, -> { where(type: nil) } - # Validations - validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category - validates_length_of :name, :in => 4..60 - validates_length_of :unit, :in => 1..15 - validates_length_of :note, :maximum => 255 - validates_length_of :origin, :maximum => 255 - validates_length_of :manufacturer, :maximum => 255 - validates_length_of :order_number, :maximum => 255 - validates_numericality_of :price, :greater_than_or_equal_to => 0 - validates_numericality_of :unit_quantity, :greater_than => 0 - validates_numericality_of :deposit, :tax - # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' } - # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity] - validate :uniqueness_of_name + scope :with_latest_versions_and_categories, lambda { + includes(:latest_article_version) + .joins(article_versions: [:article_category]) + .joins(ArticleVersion.latest_outer_join_sql("#{table_name}.#{primary_key}")) + .where(later_article_versions: { id: nil }) + } + + accepts_nested_attributes_for :latest_article_version + + # TODO-article-version: Discuss if these delegates aren't actually a code smell: + begin + ArticleVersion.column_names.each do |column_name| + next if column_name == ArticleVersion.primary_key + next if column_name == 'article_id' + + delegate column_name, "#{column_name}=", to: :latest_article_version, allow_nil: true + end + rescue StandardError + # Ignore if these delegates cannot be created (can happen if table article_versions doesn't yet exist in migrations) + end + + delegate :article_category, to: :latest_article_version, allow_nil: true + delegate :article_unit_ratios, to: :latest_article_version, allow_nil: true # Callbacks - before_save :update_price_history + before_save :update_or_create_article_version before_destroy :check_article_in_use + after_save :reload_article_on_version_change - def self.ransackable_attributes(auth_object = nil) - %w(id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number) + def self.ransackable_attributes(_auth_object = nil) + # TODO-article-version + %w[id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number] end - def self.ransackable_associations(auth_object = nil) - %w(article_category supplier order_articles orders) + def self.ransackable_associations(_auth_object = nil) + # TODO-article-version + %w[article_category supplier order_articles orders] end # Returns true if article has been updated at least 2 days ago def recently_updated - updated_at > 2.days.ago + latest_article_version.updated_at > 2.days.ago end # If the article is used in an open Order, the Order will be returned. def in_open_order @in_open_order ||= begin order_articles = OrderArticle.where(order_id: Order.open.collect(&:id)) - order_article = order_articles.detect { |oa| oa.article_id == id } + order_article = order_articles.detect { |oa| oa.article_version.article_id == id } order_article ? order_article.order : nil end end # Returns true if the article has been ordered in the given order at least once def ordered_in_order?(order) - order.order_articles.where(article_id: id).where('quantity > 0').one? + order.order_articles.includes(:article_version).where(article_version: { article_id: id }).where('quantity > 0').one? end # this method checks, if the shared_article has been changed @@ -111,15 +119,15 @@ def ordered_in_order?(order) def shared_article_changed?(supplier = self.supplier) # skip early if the timestamp hasn't changed shared_article = self.shared_article(supplier) - unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on - attrs = unequal_attributes(shared_article) - if attrs.empty? - # when attributes not changed, update timestamp of article - self.update_attribute(:shared_updated_on, shared_article.updated_on) - false - else - attrs - end + return if shared_article.nil? || shared_updated_on == shared_article.updated_on + + attrs = unequal_attributes(shared_article) + if attrs.empty? + # when attributes not changed, update timestamp of article + update_attribute(:shared_updated_on, shared_article.updated_on) + false + else + attrs end end @@ -130,81 +138,101 @@ def shared_article_changed?(supplier = self.supplier) def unequal_attributes(new_article, options = {}) # try to convert different units when desired if options[:convert_units] == false - new_price, new_unit_quantity = nil, nil + new_price = nil + new_unit_quantity = nil else new_price, new_unit_quantity = convert_units(new_article) end if new_price && new_unit_quantity - new_unit = self.unit + new_unit = unit else new_price = new_article.price new_unit_quantity = new_article.unit_quantity new_unit = new_article.unit end - return Article.compare_attributes( + ret = ArticleVersion.compare_attributes( { - :name => [self.name, new_article.name], - :manufacturer => [self.manufacturer, new_article.manufacturer.to_s], - :origin => [self.origin, new_article.origin], - :unit => [self.unit, new_unit], - :price => [self.price.to_f.round(2), new_price.to_f.round(2)], - :tax => [self.tax, new_article.tax], - :deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)], + name: [latest_article_version.name, new_article.name], + manufacturer: [latest_article_version.manufacturer, new_article.manufacturer.to_s], + origin: [latest_article_version.origin, new_article.origin], + unit: [latest_article_version.unit, new_unit], + supplier_order_unit: [latest_article_version.supplier_order_unit, new_article.supplier_order_unit], + minimum_order_quantity: [latest_article_version.minimum_order_quantity, new_article.minimum_order_quantity], + billing_unit: [latest_article_version.billing_unit || latest_article_version.supplier_order_unit, + new_article.billing_unit || new_article.supplier_order_unit], + group_order_granularity: [latest_article_version.group_order_granularity, new_article.group_order_granularity], + group_order_unit: [latest_article_version.group_order_unit, new_article.group_order_unit], + price: [latest_article_version.price.to_f.round(2), new_price.to_f.round(2)], + tax: [latest_article_version.tax, new_article.tax], + deposit: [latest_article_version.deposit.to_f.round(2), new_article.deposit.to_f.round(2)], # take care of different num-objects. - :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f], - :note => [self.note.to_s, new_article.note.to_s] + # :article_unit_ratios_attributes => [self.latest_article_version.article_unit_ratios, new_unit_quantity.article_unit_ratios], + note: [latest_article_version.note.to_s, new_article.note.to_s] } ) - end - # Compare attributes from two different articles. - # - # This is used for auto-synchronization - # @param attributes [Hash] Attributes with old and new values - # @return [Hash] Changed attributes with new values - def self.compare_attributes(attributes) - unequal_attributes = attributes.select { |name, values| values[0] != values[1] && !(values[0].blank? && values[1].blank?) } - Hash[unequal_attributes.to_a.map { |a| [a[0], a[1].last] }] + ratios_differ = latest_article_version.article_unit_ratios.length != new_article.article_unit_ratios.length || + latest_article_version.article_unit_ratios.each_with_index.any? do |article_unit_ratio, index| + new_article.article_unit_ratios[index].unit != article_unit_ratio.unit || + new_article.article_unit_ratios[index].quantity != article_unit_ratio.quantity + end + + if ratios_differ + ratio_attribs = new_article.article_unit_ratios.map(&:attributes) + ret[:article_unit_ratios_attributes] = ratio_attribs + end + + ret end # to get the correspondent shared article def shared_article(supplier = self.supplier) - self.order_number.blank? and return nil - @shared_article ||= supplier.shared_supplier.find_article_by_number(self.order_number) rescue nil + order_number.blank? and return nil + @shared_article ||= begin + supplier.shared_supplier.find_article_by_number(order_number) + rescue StandardError + nil + end end # convert units in foodcoop-size # uses unit factors in app_config.yml to calc the price/unit_quantity - # returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity] + # returns new price and unit_quantity in array, when calc is possible => [price, unit_quantity] # returns false if units aren't foodsoft-compatible # returns nil if units are eqal def convert_units(new_article = shared_article) - if unit != new_article.unit - # legacy, used by foodcoops in Germany - if new_article.unit == "KI" && unit == "ST" # 'KI' means a box, with a different amount of items in it - # try to match the size out of its name, e.g. "banana 10-12 St" => 10 - new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i - if new_unit_quantity && new_unit_quantity > 0 - new_price = (new_article.price / new_unit_quantity.to_f).round(2) - [new_price, new_unit_quantity] - else - false - end - else # use ruby-units to convert - fc_unit = (::Unit.new(unit) rescue nil) - supplier_unit = (::Unit.new(new_article.unit) rescue nil) - if fc_unit && supplier_unit && fc_unit =~ supplier_unit - conversion_factor = (supplier_unit / fc_unit).to_base.to_r - new_price = new_article.price / conversion_factor - new_unit_quantity = new_article.unit_quantity * conversion_factor - [new_price, new_unit_quantity] - else - false - end + return unless unit != new_article.unit + + # legacy, used by foodcoops in Germany + if new_article.unit == 'KI' && unit == 'ST' # 'KI' means a box, with a different amount of items in it + # try to match the size out of its name, e.g. "banana 10-12 St" => 10 + new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i + if new_unit_quantity && new_unit_quantity > 0 + new_price = (new_article.price / new_unit_quantity.to_f).round(2) + [new_price, new_unit_quantity] + else + false + end + else # use ruby-units to convert + fc_unit = begin + ::Unit.new(unit) + rescue StandardError + nil + end + supplier_unit = begin + ::Unit.new(new_article.unit) + rescue StandardError + nil + end + if fc_unit && supplier_unit && fc_unit =~ supplier_unit + conversion_factor = (supplier_unit / fc_unit).to_base.to_r + new_price = new_article.price / conversion_factor + new_unit_quantity = new_article.unit_quantity * conversion_factor + [new_price, new_unit_quantity] + else + false end - else - nil end end @@ -217,40 +245,49 @@ def mark_as_deleted update_column :deleted_at, Time.now end + def current_article_units + [supplier_order_unit, group_order_unit, billing_unit, price_unit, article_unit_ratios.map(&:unit)] + .flatten + .uniq + .compact + end + protected # Checks if the article is in use before it will deleted def check_article_in_use - raise I18n.t('articles.model.error_in_use', :article => self.name.to_s) if self.in_open_order + raise I18n.t('articles.model.error_in_use', article: name.to_s) if in_open_order end - # Create an ArticlePrice, when the price-attr are changed. - def update_price_history - if price_changed? - article_prices.build( - :price => price, - :tax => tax, - :deposit => deposit, - :unit_quantity => unit_quantity - ) - end + # Create an ArticleVersion, when the price-attr are changed. + def update_or_create_article_version + @version_changed_before_save = false + return unless version_dup_required? + + old_version = latest_article_version + new_version = old_version.duplicate_including_article_unit_ratios + article_versions << new_version + + OrderArticle.belonging_to_open_order + .joins(:article_version) + .where(article_versions: { article_id: id }) + .update_all(article_version_id: new_version.id) + + # reload old version to avoid updating it too (would automatically happen after before_save): + old_version.reload + + @version_changed_before_save = true end - def price_changed? - changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false + def reload_article_on_version_change + reload if @version_changed_before_save + @version_changed_before_save = false end - # We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all, - # this came in the way, and we now allow duplicate names for the 'all' methods - expecting foodcoops to - # make their own choice among products with different units by making articles available/unavailable. - def uniqueness_of_name - matches = Article.where(name: name, supplier_id: supplier_id, deleted_at: deleted_at, type: type) - matches = matches.where.not(id: id) unless new_record? - # supplier should always be there - except, perhaps, on initialization (on seeding) - if supplier && (supplier.shared_sync_method.blank? || supplier.shared_sync_method == 'import') - errors.add :name, :taken if matches.any? - else - errors.add :name, :taken_with_unit if matches.where(unit: unit, unit_quantity: unit_quantity).any? - end + def version_dup_required? + return false if latest_article_version.nil? + return false unless latest_article_version.self_or_ratios_changed? + + OrderArticle.belonging_to_finished_order.exists?(article_version_id: latest_article_version.id) end end diff --git a/app/models/article_BACKUP_56136.rb b/app/models/article_BACKUP_56136.rb new file mode 100644 index 00000000..41925e99 --- /dev/null +++ b/app/models/article_BACKUP_56136.rb @@ -0,0 +1,388 @@ +class Article < ApplicationRecord + include LocalizeInput + include PriceCalculation + + # @!attribute name + # @return [String] Article name + # @!attribute unit + # @return [String] Unit, e.g. +kg+, +2 L+ or +5 pieces+. + # @!attribute note + # @return [String] Short line with optional extra article information. + # @!attribute availability + # @return [Boolean] Whether this article is available within the Foodcoop. + # @!attribute manufacturer + # @return [String] Original manufacturer. + # @!attribute origin + # Where the article was produced. + # ISO 3166-1 2-letter country code, optionally prefixed with region. + # E.g. +NL+ or +Sicily, IT+ or +Berlin, DE+. + # @return [String] Production origin. + # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements + # @!attribute price + # @return [Number] Net price + # @see ArticleVersion#price + # @!attribute tax + # @return [Number] VAT percentage (10 is 10%). + # @see ArticleVersion#tax + # @!attribute deposit + # @return [Number] Deposit + # @see ArticleVersion#deposit + # @!attribute unit_quantity + # @return [Number] Number of units in wholesale package (box). + # @see ArticleVersion#unit_quantity + # @!attribute order_number + # Order number, this can be used by the supplier to identify articles. + # This is required when using the shared database functionality. + # @return [String] Order number. + # @!attribute article_category + # @return [ArticleCategory] Category this article is in. + # @!attribute supplier + # @return [Supplier] Supplier this article belongs to. + belongs_to :supplier +<<<<<<< HEAD + # @!attribute article_prices + # @return [Array] Price history (current price first). + has_many :article_prices, -> { order('created_at DESC') } + # @!attribute order_articles + # @return [Array] Order articles for this article. + has_many :order_articles +======= + # @!attribute article_versions + # @return [Array] Price history (current price first). + has_many :article_versions, -> { order("created_at DESC") } + +>>>>>>> 206a3e51 (Article units squash) + # @!attribute order + # @return [Array] Orders this article appears in. + has_many :orders, through: :order_articles + + has_one :latest_article_version, -> { merge(ArticleVersion.latest) }, foreign_key: :article_id, class_name: :ArticleVersion + + scope :undeleted, -> { where(deleted_at: nil) } + scope :available, -> { undeleted.with_latest_versions_and_categories.where(article_versions: { availability: true }) } + scope :not_in_stock, -> { where(type: nil) } + +<<<<<<< HEAD + # Validations + validates :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category, presence: true + validates :name, length: { in: 4..60 } + validates :unit, length: { in: 1..15 } + validates :note, length: { maximum: 255 } + validates :origin, length: { maximum: 255 } + validates :manufacturer, length: { maximum: 255 } + validates :order_number, length: { maximum: 255 } + validates :price, numericality: { greater_than_or_equal_to: 0 } + validates :unit_quantity, numericality: { greater_than: 0 } + validates :deposit, :tax, numericality: true + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' } + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity] + validate :uniqueness_of_name +======= + scope :with_latest_versions_and_categories, lambda { + includes(:latest_article_version) + .joins(article_versions: [:article_category]) + .joins(ArticleVersion.latest_outer_join_sql("#{table_name}.#{primary_key}")) + .where(later_article_versions: { id: nil }) + } + + accepts_nested_attributes_for :latest_article_version + + # TODO-article-version: Discuss if these delegates aren't actually a code smell: + begin + ArticleVersion.column_names.each do |column_name| + next if column_name == ArticleVersion.primary_key + next if column_name == 'article_id' + + delegate column_name, "#{column_name}=", to: :latest_article_version, allow_nil: true + end + rescue + # Ignore if these delegates cannot be created (can happen if table article_versions doesn't yet exist in migrations) + end + + delegate :article_category, to: :latest_article_version, allow_nil: true + delegate :article_unit_ratios, to: :latest_article_version, allow_nil: true +>>>>>>> 206a3e51 (Article units squash) + + # Callbacks + before_save :update_or_create_article_version + before_destroy :check_article_in_use + after_save :reload_article_on_version_change + +<<<<<<< HEAD + def self.ransackable_attributes(_auth_object = nil) + %w[id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number] + end + + def self.ransackable_associations(_auth_object = nil) + %w[article_category supplier order_articles orders] +======= + def self.ransackable_attributes(auth_object = nil) + # TODO-article-version + %w(id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number) + end + + def self.ransackable_associations(auth_object = nil) + # TODO-article-version + %w(article_category supplier order_articles orders) +>>>>>>> 206a3e51 (Article units squash) + end + + # Returns true if article has been updated at least 2 days ago + def recently_updated + latest_article_version.updated_at > 2.days.ago + end + + # If the article is used in an open Order, the Order will be returned. + def in_open_order + @in_open_order ||= begin + order_articles = OrderArticle.where(order_id: Order.open.collect(&:id)) +<<<<<<< HEAD + order_article = order_articles.detect { |oa| oa.article_id == id } + order_article&.order +======= + order_article = order_articles.detect { |oa| oa.article_version.article_id == id } + order_article ? order_article.order : nil +>>>>>>> 206a3e51 (Article units squash) + end + end + + # Returns true if the article has been ordered in the given order at least once + def ordered_in_order?(order) + order.order_articles.includes(:article_version).where(article_version: { article_id: id }).where('quantity > 0').one? + end + + # this method checks, if the shared_article has been changed + # unequal attributes will returned in array + # if only the timestamps differ and the attributes are equal, + # false will returned and self.shared_updated_on will be updated + def shared_article_changed?(supplier = self.supplier) + # skip early if the timestamp hasn't changed + shared_article = self.shared_article(supplier) + return if shared_article.nil? || shared_updated_on == shared_article.updated_on + + attrs = unequal_attributes(shared_article) + if attrs.empty? + # when attributes not changed, update timestamp of article + update_attribute(:shared_updated_on, shared_article.updated_on) + false + else + attrs + end + end + + # Return article attributes that were changed (incl. unit conversion) + # @param new_article [Article] New article to update self + # @option options [Boolean] :convert_units Omit or set to +true+ to keep current unit and recompute unit quantity and price. + # @return [Hash] Attributes with new values + def unequal_attributes(new_article, options = {}) + # try to convert different units when desired + if options[:convert_units] == false + new_price = nil + new_unit_quantity = nil + else + new_price, new_unit_quantity = convert_units(new_article) + end + if new_price && new_unit_quantity + new_unit = unit + else + new_price = new_article.price + new_unit_quantity = new_article.unit_quantity + new_unit = new_article.unit + end + +<<<<<<< HEAD + Article.compare_attributes( + { + name: [name, new_article.name], + manufacturer: [manufacturer, new_article.manufacturer.to_s], + origin: [origin, new_article.origin], + unit: [unit, new_unit], + price: [price.to_f.round(2), new_price.to_f.round(2)], + tax: [tax, new_article.tax], + deposit: [deposit.to_f.round(2), new_article.deposit.to_f.round(2)], + # take care of different num-objects. + unit_quantity: [unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f], + note: [note.to_s, new_article.note.to_s] +======= + ret = ArticleVersion.compare_attributes( + { + :name => [self.latest_article_version.name, new_article.name], + :manufacturer => [self.latest_article_version.manufacturer, new_article.manufacturer.to_s], + :origin => [self.latest_article_version.origin, new_article.origin], + :unit => [self.latest_article_version.unit, new_unit], + :supplier_order_unit => [self.latest_article_version.supplier_order_unit, new_article.supplier_order_unit], + :minimum_order_quantity => [self.latest_article_version.minimum_order_quantity, new_article.minimum_order_quantity], + :billing_unit => [self.latest_article_version.billing_unit || self.latest_article_version.supplier_order_unit, new_article.billing_unit || new_article.supplier_order_unit], + :group_order_granularity => [self.latest_article_version.group_order_granularity, new_article.group_order_granularity], + :group_order_unit => [self.latest_article_version.group_order_unit, new_article.group_order_unit], + :price => [self.latest_article_version.price.to_f.round(2), new_price.to_f.round(2)], + :tax => [self.latest_article_version.tax, new_article.tax], + :deposit => [self.latest_article_version.deposit.to_f.round(2), new_article.deposit.to_f.round(2)], + # take care of different num-objects. + # :article_unit_ratios_attributes => [self.latest_article_version.article_unit_ratios, new_unit_quantity.article_unit_ratios], + :note => [self.latest_article_version.note.to_s, new_article.note.to_s] +>>>>>>> 206a3e51 (Article units squash) + } + ) + +<<<<<<< HEAD + # Compare attributes from two different articles. + # + # This is used for auto-synchronization + # @param attributes [Hash] Attributes with old and new values + # @return [Hash] Changed attributes with new values + def self.compare_attributes(attributes) + unequal_attributes = attributes.select do |_name, values| + values[0] != values[1] && !(values[0].blank? && values[1].blank?) + end + unequal_attributes.to_a.map { |a| [a[0], a[1].last] }.to_h +======= + ratios_differ = self.latest_article_version.article_unit_ratios.length != new_article.article_unit_ratios.length || + self.latest_article_version.article_unit_ratios.each_with_index.any? do |article_unit_ratio, index| + new_article.article_unit_ratios[index].unit != article_unit_ratio.unit || + new_article.article_unit_ratios[index].quantity != article_unit_ratio.quantity + end + + if ratios_differ + ratio_attribs = new_article.article_unit_ratios.map(&:attributes) + ret[:article_unit_ratios_attributes] = ratio_attribs + end + + ret +>>>>>>> 206a3e51 (Article units squash) + end + + # to get the correspondent shared article + def shared_article(supplier = self.supplier) + order_number.blank? and return nil + @shared_article ||= begin + supplier.shared_supplier.find_article_by_number(order_number) + rescue StandardError + nil + end + end + + # convert units in foodcoop-size + # uses unit factors in app_config.yml to calc the price/unit_quantity + # returns new price and unit_quantity in array, when calc is possible => [price, unit_quantity] + # returns false if units aren't foodsoft-compatible + # returns nil if units are eqal + def convert_units(new_article = shared_article) + return unless unit != new_article.unit + return false if new_article.unit.include?(',') + + # legacy, used by foodcoops in Germany + if new_article.unit == 'KI' && unit == 'ST' # 'KI' means a box, with a different amount of items in it + # try to match the size out of its name, e.g. "banana 10-12 St" => 10 + new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i + if new_unit_quantity && new_unit_quantity > 0 + new_price = (new_article.price / new_unit_quantity.to_f).round(2) + [new_price, new_unit_quantity] + else + false + end + else # use ruby-units to convert + fc_unit = begin + ::Unit.new(unit) + rescue StandardError + nil + end + supplier_unit = begin + ::Unit.new(new_article.unit) + rescue StandardError + nil + end + if fc_unit != 0 && supplier_unit != 0 && fc_unit && supplier_unit && fc_unit =~ supplier_unit + conversion_factor = (supplier_unit / fc_unit).to_base.to_r + new_price = new_article.price / conversion_factor + new_unit_quantity = new_article.unit_quantity * conversion_factor + [new_price, new_unit_quantity] + else + false + end + end + end + + def deleted? + deleted_at.present? + end + + def mark_as_deleted + check_article_in_use + update_column :deleted_at, Time.now + end + + def current_article_units + [supplier_order_unit, group_order_unit, billing_unit, price_unit, article_unit_ratios.map(&:unit)] + .flatten + .uniq + .compact + end + + protected + + # Checks if the article is in use before it will deleted + def check_article_in_use + raise I18n.t('articles.model.error_in_use', article: name.to_s) if in_open_order + end + +<<<<<<< HEAD + # Create an ArticlePrice, when the price-attr are changed. + def update_price_history + return unless price_changed? + + article_prices.build( + price: price, + tax: tax, + deposit: deposit, + unit_quantity: unit_quantity + ) +======= + # Create an ArticleVersion, when the price-attr are changed. + def update_or_create_article_version + @version_changed_before_save = false + return unless self.version_dup_required? + + old_version = self.latest_article_version + new_version = old_version.duplicate_including_article_unit_ratios + self.article_versions << new_version + + OrderArticle.belonging_to_open_order + .joins(:article_version) + .where(article_versions: { article_id: self.id }) + .update_all(article_version_id: new_version.id) + + # reload old version to avoid updating it too (would automatically happen after before_save): + old_version.reload + + @version_changed_before_save = true +>>>>>>> 206a3e51 (Article units squash) + end + + def reload_article_on_version_change + self.reload if @version_changed_before_save + @version_changed_before_save = false + end + +<<<<<<< HEAD + # We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all, + # this came in the way, and we now allow duplicate names for the 'all' methods - expecting foodcoops to + # make their own choice among products with different units by making articles available/unavailable. + def uniqueness_of_name + matches = Article.where(name: name, supplier_id: supplier_id, deleted_at: deleted_at, type: type) + matches = matches.where.not(id: id) unless new_record? + # supplier should always be there - except, perhaps, on initialization (on seeding) + if supplier && (supplier.shared_sync_method.blank? || supplier.shared_sync_method == 'import') + errors.add :name, :taken if matches.any? + elsif matches.where(unit: unit, unit_quantity: unit_quantity).any? + errors.add :name, :taken_with_unit + end +======= + def version_dup_required? + return false if latest_article_version.nil? + return false unless latest_article_version.self_or_ratios_changed? + + OrderArticle.belonging_to_finished_order.exists?(article_version_id: latest_article_version.id) +>>>>>>> 206a3e51 (Article units squash) + end +end diff --git a/app/models/article_BASE_56136.rb b/app/models/article_BASE_56136.rb new file mode 100644 index 00000000..f8c129b6 --- /dev/null +++ b/app/models/article_BASE_56136.rb @@ -0,0 +1,256 @@ +class Article < ApplicationRecord + include PriceCalculation + + # @!attribute name + # @return [String] Article name + # @!attribute unit + # @return [String] Unit, e.g. +kg+, +2 L+ or +5 pieces+. + # @!attribute note + # @return [String] Short line with optional extra article information. + # @!attribute availability + # @return [Boolean] Whether this article is available within the Foodcoop. + # @!attribute manufacturer + # @return [String] Original manufacturer. + # @!attribute origin + # Where the article was produced. + # ISO 3166-1 2-letter country code, optionally prefixed with region. + # E.g. +NL+ or +Sicily, IT+ or +Berlin, DE+. + # @return [String] Production origin. + # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements + # @!attribute price + # @return [Number] Net price + # @see ArticlePrice#price + # @!attribute tax + # @return [Number] VAT percentage (10 is 10%). + # @see ArticlePrice#tax + # @!attribute deposit + # @return [Number] Deposit + # @see ArticlePrice#deposit + # @!attribute unit_quantity + # @return [Number] Number of units in wholesale package (box). + # @see ArticlePrice#unit_quantity + # @!attribute order_number + # Order number, this can be used by the supplier to identify articles. + # This is required when using the shared database functionality. + # @return [String] Order number. + # @!attribute article_category + # @return [ArticleCategory] Category this article is in. + belongs_to :article_category + # @!attribute supplier + # @return [Supplier] Supplier this article belongs to. + belongs_to :supplier + # @!attribute article_prices + # @return [Array] Price history (current price first). + has_many :article_prices, -> { order("created_at DESC") } + # @!attribute order_articles + # @return [Array] Order articles for this article. + has_many :order_articles + # @!attribute order + # @return [Array] Orders this article appears in. + has_many :orders, through: :order_articles + + # Replace numeric seperator with database format + localize_input_of :price, :tax, :deposit + # Get rid of unwanted whitespace. {Unit#new} may even bork on whitespace. + normalize_attributes :name, :unit, :note, :manufacturer, :origin, :order_number + + scope :undeleted, -> { where(deleted_at: nil) } + scope :available, -> { undeleted.where(availability: true) } + scope :not_in_stock, -> { where(type: nil) } + + # Validations + validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category + validates_length_of :name, :in => 4..60 + validates_length_of :unit, :in => 1..15 + validates_length_of :note, :maximum => 255 + validates_length_of :origin, :maximum => 255 + validates_length_of :manufacturer, :maximum => 255 + validates_length_of :order_number, :maximum => 255 + validates_numericality_of :price, :greater_than_or_equal_to => 0 + validates_numericality_of :unit_quantity, :greater_than => 0 + validates_numericality_of :deposit, :tax + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' } + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity] + validate :uniqueness_of_name + + # Callbacks + before_save :update_price_history + before_destroy :check_article_in_use + + def self.ransackable_attributes(auth_object = nil) + %w(id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number) + end + + def self.ransackable_associations(auth_object = nil) + %w(article_category supplier order_articles orders) + end + + # Returns true if article has been updated at least 2 days ago + def recently_updated + updated_at > 2.days.ago + end + + # If the article is used in an open Order, the Order will be returned. + def in_open_order + @in_open_order ||= begin + order_articles = OrderArticle.where(order_id: Order.open.collect(&:id)) + order_article = order_articles.detect { |oa| oa.article_id == id } + order_article ? order_article.order : nil + end + end + + # Returns true if the article has been ordered in the given order at least once + def ordered_in_order?(order) + order.order_articles.where(article_id: id).where('quantity > 0').one? + end + + # this method checks, if the shared_article has been changed + # unequal attributes will returned in array + # if only the timestamps differ and the attributes are equal, + # false will returned and self.shared_updated_on will be updated + def shared_article_changed?(supplier = self.supplier) + # skip early if the timestamp hasn't changed + shared_article = self.shared_article(supplier) + unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on + attrs = unequal_attributes(shared_article) + if attrs.empty? + # when attributes not changed, update timestamp of article + self.update_attribute(:shared_updated_on, shared_article.updated_on) + false + else + attrs + end + end + end + + # Return article attributes that were changed (incl. unit conversion) + # @param new_article [Article] New article to update self + # @option options [Boolean] :convert_units Omit or set to +true+ to keep current unit and recompute unit quantity and price. + # @return [Hash] Attributes with new values + def unequal_attributes(new_article, options = {}) + # try to convert different units when desired + if options[:convert_units] == false + new_price, new_unit_quantity = nil, nil + else + new_price, new_unit_quantity = convert_units(new_article) + end + if new_price && new_unit_quantity + new_unit = self.unit + else + new_price = new_article.price + new_unit_quantity = new_article.unit_quantity + new_unit = new_article.unit + end + + return Article.compare_attributes( + { + :name => [self.name, new_article.name], + :manufacturer => [self.manufacturer, new_article.manufacturer.to_s], + :origin => [self.origin, new_article.origin], + :unit => [self.unit, new_unit], + :price => [self.price.to_f.round(2), new_price.to_f.round(2)], + :tax => [self.tax, new_article.tax], + :deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)], + # take care of different num-objects. + :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f], + :note => [self.note.to_s, new_article.note.to_s] + } + ) + end + + # Compare attributes from two different articles. + # + # This is used for auto-synchronization + # @param attributes [Hash] Attributes with old and new values + # @return [Hash] Changed attributes with new values + def self.compare_attributes(attributes) + unequal_attributes = attributes.select { |name, values| values[0] != values[1] && !(values[0].blank? && values[1].blank?) } + Hash[unequal_attributes.to_a.map { |a| [a[0], a[1].last] }] + end + + # to get the correspondent shared article + def shared_article(supplier = self.supplier) + self.order_number.blank? and return nil + @shared_article ||= supplier.shared_supplier.find_article_by_number(self.order_number) rescue nil + end + + # convert units in foodcoop-size + # uses unit factors in app_config.yml to calc the price/unit_quantity + # returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity] + # returns false if units aren't foodsoft-compatible + # returns nil if units are eqal + def convert_units(new_article = shared_article) + if unit != new_article.unit + # legacy, used by foodcoops in Germany + if new_article.unit == "KI" && unit == "ST" # 'KI' means a box, with a different amount of items in it + # try to match the size out of its name, e.g. "banana 10-12 St" => 10 + new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i + if new_unit_quantity && new_unit_quantity > 0 + new_price = (new_article.price / new_unit_quantity.to_f).round(2) + [new_price, new_unit_quantity] + else + false + end + else # use ruby-units to convert + fc_unit = (::Unit.new(unit) rescue nil) + supplier_unit = (::Unit.new(new_article.unit) rescue nil) + if fc_unit && supplier_unit && fc_unit =~ supplier_unit + conversion_factor = (supplier_unit / fc_unit).to_base.to_r + new_price = new_article.price / conversion_factor + new_unit_quantity = new_article.unit_quantity * conversion_factor + [new_price, new_unit_quantity] + else + false + end + end + else + nil + end + end + + def deleted? + deleted_at.present? + end + + def mark_as_deleted + check_article_in_use + update_column :deleted_at, Time.now + end + + protected + + # Checks if the article is in use before it will deleted + def check_article_in_use + raise I18n.t('articles.model.error_in_use', :article => self.name.to_s) if self.in_open_order + end + + # Create an ArticlePrice, when the price-attr are changed. + def update_price_history + if price_changed? + article_prices.build( + :price => price, + :tax => tax, + :deposit => deposit, + :unit_quantity => unit_quantity + ) + end + end + + def price_changed? + changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false + end + + # We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all, + # this came in the way, and we now allow duplicate names for the 'all' methods - expecting foodcoops to + # make their own choice among products with different units by making articles available/unavailable. + def uniqueness_of_name + matches = Article.where(name: name, supplier_id: supplier_id, deleted_at: deleted_at, type: type) + matches = matches.where.not(id: id) unless new_record? + # supplier should always be there - except, perhaps, on initialization (on seeding) + if supplier && (supplier.shared_sync_method.blank? || supplier.shared_sync_method == 'import') + errors.add :name, :taken if matches.any? + else + errors.add :name, :taken_with_unit if matches.where(unit: unit, unit_quantity: unit_quantity).any? + end + end +end diff --git a/app/models/article_LOCAL_56136.rb b/app/models/article_LOCAL_56136.rb new file mode 100644 index 00000000..289be166 --- /dev/null +++ b/app/models/article_LOCAL_56136.rb @@ -0,0 +1,271 @@ +class Article < ApplicationRecord + include LocalizeInput + include PriceCalculation + + # @!attribute name + # @return [String] Article name + # @!attribute unit + # @return [String] Unit, e.g. +kg+, +2 L+ or +5 pieces+. + # @!attribute note + # @return [String] Short line with optional extra article information. + # @!attribute availability + # @return [Boolean] Whether this article is available within the Foodcoop. + # @!attribute manufacturer + # @return [String] Original manufacturer. + # @!attribute origin + # Where the article was produced. + # ISO 3166-1 2-letter country code, optionally prefixed with region. + # E.g. +NL+ or +Sicily, IT+ or +Berlin, DE+. + # @return [String] Production origin. + # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements + # @!attribute price + # @return [Number] Net price + # @see ArticlePrice#price + # @!attribute tax + # @return [Number] VAT percentage (10 is 10%). + # @see ArticlePrice#tax + # @!attribute deposit + # @return [Number] Deposit + # @see ArticlePrice#deposit + # @!attribute unit_quantity + # @return [Number] Number of units in wholesale package (box). + # @see ArticlePrice#unit_quantity + # @!attribute order_number + # Order number, this can be used by the supplier to identify articles. + # This is required when using the shared database functionality. + # @return [String] Order number. + # @!attribute article_category + # @return [ArticleCategory] Category this article is in. + belongs_to :article_category + # @!attribute supplier + # @return [Supplier] Supplier this article belongs to. + belongs_to :supplier + # @!attribute article_prices + # @return [Array] Price history (current price first). + has_many :article_prices, -> { order('created_at DESC') } + # @!attribute order_articles + # @return [Array] Order articles for this article. + has_many :order_articles + # @!attribute order + # @return [Array] Orders this article appears in. + has_many :orders, through: :order_articles + + # Replace numeric seperator with database format + localize_input_of :price, :tax, :deposit + # Get rid of unwanted whitespace. {Unit#new} may even bork on whitespace. + normalize_attributes :name, :unit, :note, :manufacturer, :origin, :order_number + + scope :undeleted, -> { where(deleted_at: nil) } + scope :available, -> { undeleted.where(availability: true) } + scope :not_in_stock, -> { where(type: nil) } + + # Validations + validates :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category, presence: true + validates :name, length: { in: 4..60 } + validates :unit, length: { in: 1..15 } + validates :note, length: { maximum: 255 } + validates :origin, length: { maximum: 255 } + validates :manufacturer, length: { maximum: 255 } + validates :order_number, length: { maximum: 255 } + validates :price, numericality: { greater_than_or_equal_to: 0 } + validates :unit_quantity, numericality: { greater_than: 0 } + validates :deposit, :tax, numericality: true + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' } + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity] + validate :uniqueness_of_name + + # Callbacks + before_save :update_price_history + before_destroy :check_article_in_use + + def self.ransackable_attributes(_auth_object = nil) + %w[id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number] + end + + def self.ransackable_associations(_auth_object = nil) + %w[article_category supplier order_articles orders] + end + + # Returns true if article has been updated at least 2 days ago + def recently_updated + updated_at > 2.days.ago + end + + # If the article is used in an open Order, the Order will be returned. + def in_open_order + @in_open_order ||= begin + order_articles = OrderArticle.where(order_id: Order.open.collect(&:id)) + order_article = order_articles.detect { |oa| oa.article_id == id } + order_article&.order + end + end + + # Returns true if the article has been ordered in the given order at least once + def ordered_in_order?(order) + order.order_articles.where(article_id: id).where('quantity > 0').one? + end + + # this method checks, if the shared_article has been changed + # unequal attributes will returned in array + # if only the timestamps differ and the attributes are equal, + # false will returned and self.shared_updated_on will be updated + def shared_article_changed?(supplier = self.supplier) + # skip early if the timestamp hasn't changed + shared_article = self.shared_article(supplier) + return if shared_article.nil? || shared_updated_on == shared_article.updated_on + + attrs = unequal_attributes(shared_article) + if attrs.empty? + # when attributes not changed, update timestamp of article + update_attribute(:shared_updated_on, shared_article.updated_on) + false + else + attrs + end + end + + # Return article attributes that were changed (incl. unit conversion) + # @param new_article [Article] New article to update self + # @option options [Boolean] :convert_units Omit or set to +true+ to keep current unit and recompute unit quantity and price. + # @return [Hash] Attributes with new values + def unequal_attributes(new_article, options = {}) + # try to convert different units when desired + if options[:convert_units] == false + new_price = nil + new_unit_quantity = nil + else + new_price, new_unit_quantity = convert_units(new_article) + end + if new_price && new_unit_quantity + new_unit = unit + else + new_price = new_article.price + new_unit_quantity = new_article.unit_quantity + new_unit = new_article.unit + end + + Article.compare_attributes( + { + name: [name, new_article.name], + manufacturer: [manufacturer, new_article.manufacturer.to_s], + origin: [origin, new_article.origin], + unit: [unit, new_unit], + price: [price.to_f.round(2), new_price.to_f.round(2)], + tax: [tax, new_article.tax], + deposit: [deposit.to_f.round(2), new_article.deposit.to_f.round(2)], + # take care of different num-objects. + unit_quantity: [unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f], + note: [note.to_s, new_article.note.to_s] + } + ) + end + + # Compare attributes from two different articles. + # + # This is used for auto-synchronization + # @param attributes [Hash] Attributes with old and new values + # @return [Hash] Changed attributes with new values + def self.compare_attributes(attributes) + unequal_attributes = attributes.select do |_name, values| + values[0] != values[1] && !(values[0].blank? && values[1].blank?) + end + unequal_attributes.to_a.map { |a| [a[0], a[1].last] }.to_h + end + + # to get the correspondent shared article + def shared_article(supplier = self.supplier) + order_number.blank? and return nil + @shared_article ||= begin + supplier.shared_supplier.find_article_by_number(order_number) + rescue StandardError + nil + end + end + + # convert units in foodcoop-size + # uses unit factors in app_config.yml to calc the price/unit_quantity + # returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity] + # returns false if units aren't foodsoft-compatible + # returns nil if units are eqal + def convert_units(new_article = shared_article) + return unless unit != new_article.unit + return false if new_article.unit.include?(',') + + # legacy, used by foodcoops in Germany + if new_article.unit == 'KI' && unit == 'ST' # 'KI' means a box, with a different amount of items in it + # try to match the size out of its name, e.g. "banana 10-12 St" => 10 + new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i + if new_unit_quantity && new_unit_quantity > 0 + new_price = (new_article.price / new_unit_quantity.to_f).round(2) + [new_price, new_unit_quantity] + else + false + end + else # use ruby-units to convert + fc_unit = begin + ::Unit.new(unit) + rescue StandardError + nil + end + supplier_unit = begin + ::Unit.new(new_article.unit) + rescue StandardError + nil + end + if fc_unit != 0 && supplier_unit != 0 && fc_unit && supplier_unit && fc_unit =~ supplier_unit + conversion_factor = (supplier_unit / fc_unit).to_base.to_r + new_price = new_article.price / conversion_factor + new_unit_quantity = new_article.unit_quantity * conversion_factor + [new_price, new_unit_quantity] + else + false + end + end + end + + def deleted? + deleted_at.present? + end + + def mark_as_deleted + check_article_in_use + update_column :deleted_at, Time.now + end + + protected + + # Checks if the article is in use before it will deleted + def check_article_in_use + raise I18n.t('articles.model.error_in_use', article: name.to_s) if in_open_order + end + + # Create an ArticlePrice, when the price-attr are changed. + def update_price_history + return unless price_changed? + + article_prices.build( + price: price, + tax: tax, + deposit: deposit, + unit_quantity: unit_quantity + ) + end + + def price_changed? + changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false + end + + # We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all, + # this came in the way, and we now allow duplicate names for the 'all' methods - expecting foodcoops to + # make their own choice among products with different units by making articles available/unavailable. + def uniqueness_of_name + matches = Article.where(name: name, supplier_id: supplier_id, deleted_at: deleted_at, type: type) + matches = matches.where.not(id: id) unless new_record? + # supplier should always be there - except, perhaps, on initialization (on seeding) + if supplier && (supplier.shared_sync_method.blank? || supplier.shared_sync_method == 'import') + errors.add :name, :taken if matches.any? + elsif matches.where(unit: unit, unit_quantity: unit_quantity).any? + errors.add :name, :taken_with_unit + end + end +end diff --git a/app/models/article_REMOTE_56136.rb b/app/models/article_REMOTE_56136.rb new file mode 100644 index 00000000..46aeb014 --- /dev/null +++ b/app/models/article_REMOTE_56136.rb @@ -0,0 +1,279 @@ +class Article < ApplicationRecord + include PriceCalculation + + # @!attribute name + # @return [String] Article name + # @!attribute unit + # @return [String] Unit, e.g. +kg+, +2 L+ or +5 pieces+. + # @!attribute note + # @return [String] Short line with optional extra article information. + # @!attribute availability + # @return [Boolean] Whether this article is available within the Foodcoop. + # @!attribute manufacturer + # @return [String] Original manufacturer. + # @!attribute origin + # Where the article was produced. + # ISO 3166-1 2-letter country code, optionally prefixed with region. + # E.g. +NL+ or +Sicily, IT+ or +Berlin, DE+. + # @return [String] Production origin. + # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements + # @!attribute price + # @return [Number] Net price + # @see ArticleVersion#price + # @!attribute tax + # @return [Number] VAT percentage (10 is 10%). + # @see ArticleVersion#tax + # @!attribute deposit + # @return [Number] Deposit + # @see ArticleVersion#deposit + # @!attribute unit_quantity + # @return [Number] Number of units in wholesale package (box). + # @see ArticleVersion#unit_quantity + # @!attribute order_number + # Order number, this can be used by the supplier to identify articles. + # This is required when using the shared database functionality. + # @return [String] Order number. + # @!attribute article_category + # @return [ArticleCategory] Category this article is in. + # @!attribute supplier + # @return [Supplier] Supplier this article belongs to. + belongs_to :supplier + # @!attribute article_versions + # @return [Array] Price history (current price first). + has_many :article_versions, -> { order("created_at DESC") } + + # @!attribute order + # @return [Array] Orders this article appears in. + has_many :orders, through: :order_articles + + has_one :latest_article_version, -> { merge(ArticleVersion.latest) }, foreign_key: :article_id, class_name: :ArticleVersion + + scope :undeleted, -> { where(deleted_at: nil) } + scope :available, -> { undeleted.with_latest_versions_and_categories.where(article_versions: { availability: true }) } + scope :not_in_stock, -> { where(type: nil) } + + scope :with_latest_versions_and_categories, lambda { + includes(:latest_article_version) + .joins(article_versions: [:article_category]) + .joins(ArticleVersion.latest_outer_join_sql("#{table_name}.#{primary_key}")) + .where(later_article_versions: { id: nil }) + } + + accepts_nested_attributes_for :latest_article_version + + # TODO-article-version: Discuss if these delegates aren't actually a code smell: + begin + ArticleVersion.column_names.each do |column_name| + next if column_name == ArticleVersion.primary_key + next if column_name == 'article_id' + + delegate column_name, "#{column_name}=", to: :latest_article_version, allow_nil: true + end + rescue + # Ignore if these delegates cannot be created (can happen if table article_versions doesn't yet exist in migrations) + end + + delegate :article_category, to: :latest_article_version, allow_nil: true + delegate :article_unit_ratios, to: :latest_article_version, allow_nil: true + + # Callbacks + before_save :update_or_create_article_version + before_destroy :check_article_in_use + after_save :reload_article_on_version_change + + def self.ransackable_attributes(auth_object = nil) + # TODO-article-version + %w(id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number) + end + + def self.ransackable_associations(auth_object = nil) + # TODO-article-version + %w(article_category supplier order_articles orders) + end + + # Returns true if article has been updated at least 2 days ago + def recently_updated + latest_article_version.updated_at > 2.days.ago + end + + # If the article is used in an open Order, the Order will be returned. + def in_open_order + @in_open_order ||= begin + order_articles = OrderArticle.where(order_id: Order.open.collect(&:id)) + order_article = order_articles.detect { |oa| oa.article_version.article_id == id } + order_article ? order_article.order : nil + end + end + + # Returns true if the article has been ordered in the given order at least once + def ordered_in_order?(order) + order.order_articles.includes(:article_version).where(article_version: { article_id: id }).where('quantity > 0').one? + end + + # this method checks, if the shared_article has been changed + # unequal attributes will returned in array + # if only the timestamps differ and the attributes are equal, + # false will returned and self.shared_updated_on will be updated + def shared_article_changed?(supplier = self.supplier) + # skip early if the timestamp hasn't changed + shared_article = self.shared_article(supplier) + unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on + attrs = unequal_attributes(shared_article) + if attrs.empty? + # when attributes not changed, update timestamp of article + self.update_attribute(:shared_updated_on, shared_article.updated_on) + false + else + attrs + end + end + end + + # Return article attributes that were changed (incl. unit conversion) + # @param new_article [Article] New article to update self + # @option options [Boolean] :convert_units Omit or set to +true+ to keep current unit and recompute unit quantity and price. + # @return [Hash] Attributes with new values + def unequal_attributes(new_article, options = {}) + # try to convert different units when desired + if options[:convert_units] == false + new_price, new_unit_quantity = nil, nil + else + new_price, new_unit_quantity = convert_units(new_article) + end + if new_price && new_unit_quantity + new_unit = self.unit + else + new_price = new_article.price + new_unit_quantity = new_article.unit_quantity + new_unit = new_article.unit + end + + ret = ArticleVersion.compare_attributes( + { + :name => [self.latest_article_version.name, new_article.name], + :manufacturer => [self.latest_article_version.manufacturer, new_article.manufacturer.to_s], + :origin => [self.latest_article_version.origin, new_article.origin], + :unit => [self.latest_article_version.unit, new_unit], + :supplier_order_unit => [self.latest_article_version.supplier_order_unit, new_article.supplier_order_unit], + :minimum_order_quantity => [self.latest_article_version.minimum_order_quantity, new_article.minimum_order_quantity], + :billing_unit => [self.latest_article_version.billing_unit || self.latest_article_version.supplier_order_unit, new_article.billing_unit || new_article.supplier_order_unit], + :group_order_granularity => [self.latest_article_version.group_order_granularity, new_article.group_order_granularity], + :group_order_unit => [self.latest_article_version.group_order_unit, new_article.group_order_unit], + :price => [self.latest_article_version.price.to_f.round(2), new_price.to_f.round(2)], + :tax => [self.latest_article_version.tax, new_article.tax], + :deposit => [self.latest_article_version.deposit.to_f.round(2), new_article.deposit.to_f.round(2)], + # take care of different num-objects. + # :article_unit_ratios_attributes => [self.latest_article_version.article_unit_ratios, new_unit_quantity.article_unit_ratios], + :note => [self.latest_article_version.note.to_s, new_article.note.to_s] + } + ) + + ratios_differ = self.latest_article_version.article_unit_ratios.length != new_article.article_unit_ratios.length || + self.latest_article_version.article_unit_ratios.each_with_index.any? do |article_unit_ratio, index| + new_article.article_unit_ratios[index].unit != article_unit_ratio.unit || + new_article.article_unit_ratios[index].quantity != article_unit_ratio.quantity + end + + if ratios_differ + ratio_attribs = new_article.article_unit_ratios.map(&:attributes) + ret[:article_unit_ratios_attributes] = ratio_attribs + end + + ret + end + + # to get the correspondent shared article + def shared_article(supplier = self.supplier) + self.order_number.blank? and return nil + @shared_article ||= supplier.shared_supplier.find_article_by_number(self.order_number) rescue nil + end + + # convert units in foodcoop-size + # uses unit factors in app_config.yml to calc the price/unit_quantity + # returns new price and unit_quantity in array, when calc is possible => [price, unit_quantity] + # returns false if units aren't foodsoft-compatible + # returns nil if units are eqal + def convert_units(new_article = shared_article) + if unit != new_article.unit + # legacy, used by foodcoops in Germany + if new_article.unit == "KI" && unit == "ST" # 'KI' means a box, with a different amount of items in it + # try to match the size out of its name, e.g. "banana 10-12 St" => 10 + new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i + if new_unit_quantity && new_unit_quantity > 0 + new_price = (new_article.price / new_unit_quantity.to_f).round(2) + [new_price, new_unit_quantity] + else + false + end + else # use ruby-units to convert + fc_unit = (::Unit.new(unit) rescue nil) + supplier_unit = (::Unit.new(new_article.unit) rescue nil) + if fc_unit && supplier_unit && fc_unit =~ supplier_unit + conversion_factor = (supplier_unit / fc_unit).to_base.to_r + new_price = new_article.price / conversion_factor + new_unit_quantity = new_article.unit_quantity * conversion_factor + [new_price, new_unit_quantity] + else + false + end + end + else + nil + end + end + + def deleted? + deleted_at.present? + end + + def mark_as_deleted + check_article_in_use + update_column :deleted_at, Time.now + end + + def current_article_units + [supplier_order_unit, group_order_unit, billing_unit, price_unit, article_unit_ratios.map(&:unit)] + .flatten + .uniq + .compact + end + + protected + + # Checks if the article is in use before it will deleted + def check_article_in_use + raise I18n.t('articles.model.error_in_use', :article => self.name.to_s) if self.in_open_order + end + + # Create an ArticleVersion, when the price-attr are changed. + def update_or_create_article_version + @version_changed_before_save = false + return unless self.version_dup_required? + + old_version = self.latest_article_version + new_version = old_version.duplicate_including_article_unit_ratios + self.article_versions << new_version + + OrderArticle.belonging_to_open_order + .joins(:article_version) + .where(article_versions: { article_id: self.id }) + .update_all(article_version_id: new_version.id) + + # reload old version to avoid updating it too (would automatically happen after before_save): + old_version.reload + + @version_changed_before_save = true + end + + def reload_article_on_version_change + self.reload if @version_changed_before_save + @version_changed_before_save = false + end + + def version_dup_required? + return false if latest_article_version.nil? + return false unless latest_article_version.self_or_ratios_changed? + + OrderArticle.belonging_to_finished_order.exists?(article_version_id: latest_article_version.id) + end +end diff --git a/app/models/article_category.rb b/app/models/article_category.rb index 28597a59..1574b5d5 100644 --- a/app/models/article_category.rb +++ b/app/models/article_category.rb @@ -17,16 +17,16 @@ class ArticleCategory < ApplicationRecord normalize_attributes :name, :description - validates :name, :presence => true, :uniqueness => true, :length => { :minimum => 2 } + validates :name, presence: true, uniqueness: true, length: { minimum: 2 } before_destroy :check_for_associated_articles - def self.ransackable_attributes(auth_object = nil) - %w(id name) + def self.ransackable_attributes(_auth_object = nil) + %w[id name] end - def self.ransackable_associations(auth_object = nil) - %w(articles order_articles orders) + def self.ransackable_associations(_auth_object = nil) + %w[articles order_articles orders] end # Find a category that matches a category name; may return nil. @@ -40,7 +40,11 @@ def self.find_match(category) # case-insensitive substring match (take the closest match = shortest) c = ArticleCategory.where('name LIKE ?', "%#{category}%") unless c && c.any? # case-insensitive phrase present in category description - c = ArticleCategory.where('description LIKE ?', "%#{category}%").select { |s| s.description.match /(^|,)\s*#{category}\s*(,|$)/i } unless c && c.any? + unless c && c.any? + c = ArticleCategory.where('description LIKE ?', "%#{category}%").select do |s| + s.description.match(/(^|,)\s*#{category}\s*(,|$)/i) + end + end # return closest match if there are multiple c = c.sort_by { |s| s.name.length }.first if c.respond_to? :sort_by c @@ -50,6 +54,9 @@ def self.find_match(category) # Deny deleting the category when there are associated articles. def check_for_associated_articles - raise I18n.t('activerecord.errors.has_many_left', collection: Article.model_name.human) if articles.undeleted.exists? + return unless articles.undeleted.exists? + + raise I18n.t('activerecord.errors.has_many_left', + collection: Article.model_name.human) end end diff --git a/app/models/article_price.rb b/app/models/article_price.rb deleted file mode 100644 index cca4c200..00000000 --- a/app/models/article_price.rb +++ /dev/null @@ -1,30 +0,0 @@ -class ArticlePrice < ApplicationRecord - include PriceCalculation - - # @!attribute price - # @return [Number] Net price - # @see Article#price - # @!attribute tax - # @return [Number] VAT percentage - # @see Article#tax - # @!attribute deposit - # @return [Number] Deposit - # @see Article#deposit - # @!attribute unit_quantity - # @return [Number] Number of units in wholesale package (box). - # @see Article#unit - # @see Article#unit_quantity - # @!attribute article - # @return [Article] Article this price is about. - belongs_to :article - # @!attribute order_articles - # @return [Array] Order articles this price is associated with. - has_many :order_articles - - localize_input_of :price, :tax, :deposit - - validates_presence_of :price, :tax, :deposit, :unit_quantity - validates_numericality_of :price, :greater_than_or_equal_to => 0 - validates_numericality_of :unit_quantity, :greater_than => 0 - validates_numericality_of :deposit, :tax -end diff --git a/app/models/article_unit.rb b/app/models/article_unit.rb new file mode 100644 index 00000000..296fce54 --- /dev/null +++ b/app/models/article_unit.rb @@ -0,0 +1,43 @@ +class ArticleUnit < ApplicationRecord + self.primary_key = :unit + + before_save { ArticleUnit.clear_cache } + before_destroy { ArticleUnit.clear_cache } + + def self.all_cached + @all_cached = {} if @all_cached.nil? + cached_units_in_locale = @all_cached[I18n.locale] + return cached_units_in_locale unless cached_units_in_locale.nil? + + @all_cached[I18n.locale] = all.load + end + + def self.clear_cache + @all_cached = {} + end + + def self.as_hash(config = nil) + additional_units = config&.dig(:additional_units) || [] + available_units = all_cached.map(&:unit) + ArticleUnitsLib.units.to_h do |code, unit| + [code, unit.merge({ visible: available_units.include?(code) || additional_units.include?(code) })] + end + end + + def self.as_options(config = nil) + additional_units = config&.dig(:additional_units) || [] + options = {} + + available_units = all_cached.map(&:unit) + ArticleUnitsLib.units.each do |code, unit| + next unless available_units.include?(code) || additional_units.include?(code) + + label = unit[:name] + label += " (#{unit[:symbol]})" if unit[:symbol].present? + + options[label] = code + end + + options + end +end diff --git a/app/models/article_unit_ratio.rb b/app/models/article_unit_ratio.rb new file mode 100644 index 00000000..be007f98 --- /dev/null +++ b/app/models/article_unit_ratio.rb @@ -0,0 +1,6 @@ +class ArticleUnitRatio < ApplicationRecord + belongs_to :article_version + + validates :quantity, :sort, :unit, presence: true + validates :quantity, numericality: { greater_than: 0 } +end diff --git a/app/models/article_version.rb b/app/models/article_version.rb new file mode 100644 index 00000000..e7401917 --- /dev/null +++ b/app/models/article_version.rb @@ -0,0 +1,177 @@ +class ArticleVersion < ApplicationRecord + include PriceCalculation + + # @!attribute price + # @return [Number] Net price + # @see Article#price + # @!attribute tax + # @return [Number] VAT percentage + # @see Article#tax + # @!attribute deposit + # @return [Number] Deposit + # @see Article#deposit + # @!attribute unit_quantity + # @return [Number] Number of units in wholesale package (box). + # @see Article#unit + # @see Article#unit_quantity + # @!attribute article + # @return [Article] Article this price is about. + belongs_to :article + belongs_to :article_category + # @!attribute order_articles + # @return [Array] Order articles this price is associated with. + has_many :order_articles + + has_many :article_unit_ratios, after_add: :on_article_unit_ratios_change, + after_remove: :on_article_unit_ratios_change, dependent: :destroy + + localize_input_of :price, :tax, :deposit + + # Validations + validates :name, :price, :tax, :deposit, :article_category, presence: true + validates :name, length: { in: 4..60 } + validates :unit, length: { in: 1..15, unless: :supplier_order_unit } + validates :supplier_order_unit, presence: { unless: :unit } + validates :note, length: { maximum: 255 } + validates :origin, length: { maximum: 255 } + validates :manufacturer, length: { maximum: 255 } + validates :order_number, length: { maximum: 255 } + validates :price, numericality: { greater_than_or_equal_to: 0 } + validates :group_order_granularity, numericality: { greater_than_or_equal_to: 0 } + validates :deposit, :tax, numericality: true + validates :minimum_order_quantity, + numericality: { allow_nil: true, only_integer: false, if: :supplier_order_unit_is_si_convertible } + validates :minimum_order_quantity, + numericality: { allow_nil: true, only_integer: true, unless: :supplier_order_unit_is_si_convertible } + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' } + # validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity] + validate :uniqueness_of_name + validate :only_one_unit_type + + # Replace numeric seperator with database format + localize_input_of :price, :tax, :deposit + # Get rid of unwanted whitespace. {Unit#new} may even bork on whitespace. + normalize_attributes :name, :unit, :note, :manufacturer, :origin, :order_number + + accepts_nested_attributes_for :article_unit_ratios, allow_destroy: true + + scope :latest, lambda { + joins(latest_outer_join_sql("#{table_name}.article_id")).where(later_article_versions: { id: nil }) + } + + def self.latest_outer_join_sql(article_field_name) + %( + LEFT OUTER JOIN article_versions later_article_versions + ON later_article_versions.article_id = #{article_field_name} + AND later_article_versions.created_at > article_versions.created_at + ) + end + + def supplier_order_unit_is_si_convertible + ArticleUnitsLib.unit_is_si_convertible(supplier_order_unit) + end + + # TODO: Maybe use the nilify blanks gem instead of the following six methods?: + def unit=(value) + if value.blank? + self[:unit] = nil + else + super + end + end + + def supplier_order_unit=(value) + if value.blank? + self[:supplier_order_unit] = nil + else + super + end + end + + def group_order_unit=(value) + if value.blank? + self[:group_order_unit] = nil + else + super + end + end + + def price_unit=(value) + if value.blank? + self[:price_unit] = nil + else + super + end + end + + def billing_unit=(value) + if value.blank? + self[:billing_unit] = nil + else + super + end + end + + def minimum_order_quantity=(value) + if value.blank? + self[:minimum_order_quantity] = nil + else + super + end + end + + def self_or_ratios_changed? + changed? || @article_unit_ratios_changed || article_unit_ratios.any?(&:changed?) + end + + def duplicate_including_article_unit_ratios + new_version = dup + article_unit_ratios.each do |ratio| + ratio = ratio.dup + ratio.article_version_id = nil + new_version.article_unit_ratios << ratio + end + + new_version + end + + # Compare attributes from two different articles. + # + # This is used for auto-synchronization + # @param attributes [Hash] Attributes with old and new values + # @return [Hash] Changed attributes with new values + def self.compare_attributes(attributes) + unequal_attributes = attributes.select do |_name, values| + values[0] != values[1] && !(values[0].blank? && values[1].blank?) + end + unequal_attributes.to_a.map { |a| [a[0], a[1].last] }.to_h + end + + protected + + # We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all, + # this came in the way, and we now allow duplicate names for the 'all' methods - expecting foodcoops to + # make their own choice among products with different units by making articles available/unavailable. + def uniqueness_of_name + matches = Article.includes(latest_article_version: :article_unit_ratios).where(article_versions: { name: name }, + supplier_id: article.supplier_id, deleted_at: article.deleted_at, type: article.type) + matches = matches.where.not(id: article.id) unless article.new_record? + # supplier should always be there - except, perhaps, on initialization (on seeding) + if article.supplier && (article.supplier.shared_sync_method.blank? || article.supplier.shared_sync_method == 'import') + errors.add :name, :taken if matches.any? + elsif matches.where(article_versions: { unit: unit, supplier_order_unit: nil, + article_unit_ratios: { quantity: unit_quantity, unit: 'XPP' } }).any? + errors.add :name, :taken_with_unit + end + end + + def only_one_unit_type + return if unit.blank? || supplier_order_unit.blank? + + errors.add :unit # not specifying a specific error message as this should be prevented by js + end + + def on_article_unit_ratios_change(_some_change) + @article_unit_ratios_changed = true + end +end diff --git a/app/models/bank_account.rb b/app/models/bank_account.rb index de15ee4b..92cefad3 100644 --- a/app/models/bank_account.rb +++ b/app/models/bank_account.rb @@ -5,23 +5,21 @@ class BankAccount < ApplicationRecord normalize_attributes :name, :iban, :description - validates :name, :presence => true, :uniqueness => true, :length => { :minimum => 2 } - validates :iban, :presence => true, :uniqueness => true - validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/ - validates_numericality_of :balance, :message => I18n.t('bank_account.model.invalid_balance') + validates :name, presence: true, uniqueness: true, length: { minimum: 2 } + validates :iban, presence: true, uniqueness: true + validates :iban, format: { with: /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/ } + validates :balance, numericality: { message: I18n.t('bank_account.model.invalid_balance') } # @return [Function] Method wich can be called to import transaction from a bank or nil if unsupported def find_connector klass = BankAccountConnector.find iban - return klass.new self if klass + klass.new self if klass end def assign_unlinked_transactions count = 0 bank_transactions.without_financial_link.includes(:supplier, :user).each do |t| - if t.assign_to_ordergroup || t.assign_to_invoice - count += 1 - end + count += 1 if t.assign_to_ordergroup || t.assign_to_invoice end count end diff --git a/app/models/bank_gateway.rb b/app/models/bank_gateway.rb index 3811f128..f8043755 100644 --- a/app/models/bank_gateway.rb +++ b/app/models/bank_gateway.rb @@ -4,5 +4,5 @@ class BankGateway < ApplicationRecord scope :with_unattended_support, -> { where.not(unattended_user: nil) } - validates_presence_of :name, :url + validates :name, :url, presence: true end diff --git a/app/models/bank_transaction.rb b/app/models/bank_transaction.rb index fa1a3da5..66454b32 100644 --- a/app/models/bank_transaction.rb +++ b/app/models/bank_transaction.rb @@ -21,8 +21,8 @@ class BankTransaction < ApplicationRecord belongs_to :supplier, optional: true, foreign_key: 'iban', primary_key: 'iban' belongs_to :user, optional: true, foreign_key: 'iban', primary_key: 'iban' - validates_presence_of :date, :amount, :bank_account_id - validates_numericality_of :amount + validates :date, :amount, :bank_account_id, presence: true + validates :amount, numericality: true scope :without_financial_link, -> { where(financial_link: nil) } @@ -30,13 +30,13 @@ class BankTransaction < ApplicationRecord localize_input_of :amount def image_url - 'data:image/png;base64,' + Base64.encode64(self.image) + 'data:image/png;base64,' + Base64.encode64(image) end def assign_to_invoice return false unless supplier - content = text || "" + content = text || '' content += "\n" + reference if reference.present? invoices = supplier.invoices.unpaid.select { |i| content.include? i.number } invoices_sum = invoices.map(&:amount).sum @@ -48,7 +48,7 @@ def assign_to_invoice update_attribute :financial_link, link end - return true + true end def assign_to_ordergroup @@ -77,6 +77,6 @@ def assign_to_ordergroup update_attribute :financial_link, link end - return true + true end end diff --git a/app/models/concerns/custom_fields.rb b/app/models/concerns/custom_fields.rb index d54cebe5..aafec389 100644 --- a/app/models/concerns/custom_fields.rb +++ b/app/models/concerns/custom_fields.rb @@ -10,7 +10,7 @@ module CustomFields end after_save do - self.settings.custom_fields = custom_fields if custom_fields + settings.custom_fields = custom_fields if custom_fields end end end diff --git a/app/models/concerns/find_each_with_order.rb b/app/models/concerns/find_each_with_order.rb index 0e7cd5cd..faf545b2 100644 --- a/app/models/concerns/find_each_with_order.rb +++ b/app/models/concerns/find_each_with_order.rb @@ -3,9 +3,9 @@ module FindEachWithOrder extend ActiveSupport::Concern class_methods do - def find_each_with_order(options = {}) + def find_each_with_order(options = {}, &block) find_in_batches_with_order(options) do |records| - records.each { |record| yield record } + records.each(&block) end end diff --git a/app/models/concerns/mark_as_deleted_with_name.rb b/app/models/concerns/mark_as_deleted_with_name.rb index 4b888438..fb0aa590 100644 --- a/app/models/concerns/mark_as_deleted_with_name.rb +++ b/app/models/concerns/mark_as_deleted_with_name.rb @@ -3,7 +3,7 @@ module MarkAsDeletedWithName def mark_as_deleted # get maximum length of name - max_length = 100000 + max_length = 100_000 if lenval = self.class.validators_on(:name).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) } max_length = lenval.options[:maximum] end diff --git a/app/models/concerns/price_calculation.rb b/app/models/concerns/price_calculation.rb index 03b9a7ad..b88fb2d8 100644 --- a/app/models/concerns/price_calculation.rb +++ b/app/models/concerns/price_calculation.rb @@ -1,6 +1,15 @@ module PriceCalculation extend ActiveSupport::Concern + def unit_quantity + first_ratio = article_unit_ratios.first + if first_ratio.nil? + 1 + else + first_ratio.quantity + end + end + # Gross price = net price + deposit + tax. # @return [Number] Gross price. def gross_price @@ -12,9 +21,42 @@ def fc_price add_percent(gross_price, FoodsoftConfig[:price_markup]) end + # get the unit ratio quantity in reference to the supplier_order_unit + def get_unit_ratio_quantity(unit) + return 1 if unit == supplier_order_unit + + ratio = article_unit_ratios.find_by_unit(unit) + return ratio.quantity unless ratio.nil? + + related_ratio = article_unit_ratios.detect do |current_ratio| + ArticleUnit.as_hash[current_ratio.unit][:baseUnit] == ArticleUnit.as_hash[unit][:baseUnit] + end + return related_ratio.quantity / ArticleUnit.as_hash[unit][:conversionFactor] * ArticleUnit.as_hash[related_ratio.unit][:conversionFactor] unless related_ratio.nil? + + ArticleUnit.as_hash[supplier_order_unit][:conversionFactor] / ArticleUnit.as_hash[unit][:conversionFactor] + end + + def convert_quantity(quantity, input_unit, output_unit) + quantity / get_unit_ratio_quantity(input_unit) * get_unit_ratio_quantity(output_unit) + end + + def group_order_price(value = nil) + value ||= price + # price is always stored in supplier_order_unit: + value / convert_quantity(1, supplier_order_unit, group_order_unit) + end + + def gross_group_order_price + group_order_price(gross_price) + end + + def fc_group_order_price + group_order_price(fc_price) + end + private def add_percent(value, percent) - (value * (percent * 0.01 + 1)).round(2) + (value * ((percent * 0.01) + 1)).round(2) end end diff --git a/app/models/delivery.rb b/app/models/delivery.rb index ab5ca5ec..bb2aed45 100644 --- a/app/models/delivery.rb +++ b/app/models/delivery.rb @@ -4,10 +4,10 @@ class Delivery < StockEvent scope :recent, -> { order('created_at DESC').limit(10) } - validates_presence_of :supplier_id + validates :supplier_id, presence: true validate :stock_articles_must_be_unique - accepts_nested_attributes_for :stock_changes, :allow_destroy => :true + accepts_nested_attributes_for :stock_changes, allow_destroy: :true def new_stock_changes=(stock_change_attributes) for attributes in stock_change_attributes @@ -16,7 +16,7 @@ def new_stock_changes=(stock_change_attributes) end def includes_article?(article) - self.stock_changes.map { |stock_change| stock_change.stock_article.id }.include? article.id + stock_changes.map { |stock_change| stock_change.stock_article.id }.include? article.id end def sum(type = :gross) @@ -39,8 +39,8 @@ def sum(type = :gross) protected def stock_articles_must_be_unique - unless stock_changes.reject { |sc| sc.marked_for_destruction? }.map { |sc| sc.stock_article.id }.uniq!.nil? - errors.add(:base, I18n.t('model.delivery.each_stock_article_must_be_unique')) - end + return if stock_changes.reject { |sc| sc.marked_for_destruction? }.map { |sc| sc.stock_article.id }.uniq!.nil? + + errors.add(:base, I18n.t('model.delivery.each_stock_article_must_be_unique')) end end diff --git a/app/models/financial_link.rb b/app/models/financial_link.rb index 30a1955c..51108cd2 100644 --- a/app/models/financial_link.rb +++ b/app/models/financial_link.rb @@ -4,13 +4,13 @@ class FinancialLink < ApplicationRecord has_many :invoices scope :incomplete, -> { with_full_sum.where.not('full_sums.full_sum' => 0) } - scope :unused, -> { + scope :unused, lambda { includes(:bank_transactions, :financial_transactions, :invoices) .where(bank_transactions: { financial_link_id: nil }) .where(financial_transactions: { financial_link_id: nil }) .where(invoices: { financial_link_id: nil }) } - scope :with_full_sum, -> { + scope :with_full_sum, lambda { select(:id, :note, :full_sum).joins(<<-SQL) LEFT JOIN ( SELECT id, COALESCE(bt_sum, 0) - COALESCE(ft_sum, 0) + COALESCE(i_sum, 0) AS full_sum diff --git a/app/models/financial_transaction.rb b/app/models/financial_transaction.rb index 5c26058b..68882d0d 100644 --- a/app/models/financial_transaction.rb +++ b/app/models/financial_transaction.rb @@ -6,14 +6,16 @@ class FinancialTransaction < ApplicationRecord belongs_to :financial_link, optional: true belongs_to :financial_transaction_type belongs_to :group_order, optional: true - belongs_to :reverts, optional: true, class_name: 'FinancialTransaction', foreign_key: 'reverts_id' + belongs_to :reverts, optional: true, class_name: 'FinancialTransaction' has_one :reverted_by, class_name: 'FinancialTransaction', foreign_key: 'reverts_id' - validates_presence_of :amount, :note, :user_id - validates_numericality_of :amount, greater_then: -100_000, - less_than: 100_000 + validates :amount, :note, :user_id, presence: true + validates :amount, numericality: { greater_then: -100_000, + less_than: 100_000 } - scope :visible, -> { joins('LEFT JOIN financial_transactions r ON financial_transactions.id = r.reverts_id').where('r.id IS NULL').where(reverts: nil) } + scope :visible, lambda { + joins('LEFT JOIN financial_transactions r ON financial_transactions.id = r.reverts_id').where('r.id IS NULL').where(reverts: nil) + } scope :without_financial_link, -> { where(financial_link: nil) } scope :with_ordergroup, -> { where.not(ordergroup: nil) } @@ -26,12 +28,12 @@ class FinancialTransaction < ApplicationRecord # @todo remove alias (and rename created_on to created_at below) after #575 ransack_alias :created_at, :created_on - def self.ransackable_attributes(auth_object = nil) - %w(id amount note created_on user_id) + def self.ransackable_attributes(_auth_object = nil) + %w[id amount note created_on user_id] end - def self.ransackable_associations(auth_object = nil) - %w() # none, and certainly not user until we've secured that more + def self.ransackable_associations(_auth_object = nil) + %w[] # none, and certainly not user until we've secured that more end # Use this save method instead of simple save and after callback diff --git a/app/models/financial_transaction_class.rb b/app/models/financial_transaction_class.rb index 43ded5fd..0c924993 100644 --- a/app/models/financial_transaction_class.rb +++ b/app/models/financial_transaction_class.rb @@ -5,7 +5,7 @@ class FinancialTransactionClass < ApplicationRecord has_many :ordergroups, -> { distinct }, through: :financial_transactions validates :name, presence: true - validates_uniqueness_of :name + validates :name, uniqueness: true after_save :update_balance_of_ordergroups diff --git a/app/models/financial_transaction_type.rb b/app/models/financial_transaction_type.rb index 392a1a95..97ed7979 100644 --- a/app/models/financial_transaction_type.rb +++ b/app/models/financial_transaction_type.rb @@ -5,13 +5,13 @@ class FinancialTransactionType < ApplicationRecord has_many :ordergroups, -> { distinct }, through: :financial_transactions validates :name, presence: true - validates_uniqueness_of :name - validates_uniqueness_of :name_short, allow_blank: true, allow_nil: true - validates_format_of :name_short, :with => /\A[A-Za-z]*\z/ + validates :name, uniqueness: true + validates :name_short, uniqueness: { allow_blank: true } + validates :name_short, format: { with: /\A[A-Za-z]*\z/ } validates :financial_transaction_class, presence: true - after_save :update_balance_of_ordergroups before_destroy :restrict_deleting_last_financial_transaction_type + after_save :update_balance_of_ordergroups scope :with_name_short, -> { where.not(name_short: [nil, '']) } @@ -20,7 +20,7 @@ def self.default end def self.has_multiple_types - self.count > 1 + count > 1 end protected diff --git a/app/models/group.rb b/app/models/group.rb index a667ea5a..a4a770eb 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -7,8 +7,8 @@ class Group < ApplicationRecord has_many :memberships, dependent: :destroy has_many :users, -> { where(deleted_at: nil) }, through: :memberships - validates :name, :presence => true, :length => { :in => 1..25 } - validates_uniqueness_of :name + validates :name, presence: true, length: { in: 1..25 } + validates :name, uniqueness: true attr_reader :user_tokens @@ -25,7 +25,7 @@ def non_members end def user_tokens=(ids) - self.user_ids = ids.split(",") + self.user_ids = ids.split(',') end def deleted? diff --git a/app/models/group_order.rb b/app/models/group_order.rb index c789ef4e..b48c9f71 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -6,14 +6,14 @@ class GroupOrder < ApplicationRecord belongs_to :order belongs_to :ordergroup, optional: true - has_many :group_order_articles, :dependent => :destroy - has_many :order_articles, :through => :group_order_articles + has_many :group_order_articles, dependent: :destroy + has_many :order_articles, through: :group_order_articles has_one :financial_transaction belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id' - validates_presence_of :order_id - validates_numericality_of :price - validates_uniqueness_of :ordergroup_id, :scope => :order_id # order groups can only order once per order + validates :order_id, presence: true + validates :price, numericality: true + validates :ordergroup_id, uniqueness: { scope: :order_id } # order groups can only order once per order scope :in_open_orders, -> { joins(:order).merge(Order.open) } scope :in_finished_orders, -> { joins(:order).merge(Order.finished_not_closed) } @@ -21,40 +21,47 @@ class GroupOrder < ApplicationRecord scope :ordered, -> { includes(:ordergroup).order('groups.name') } - def self.ransackable_attributes(auth_object = nil) - %w(id price) + def self.ransackable_attributes(_auth_object = nil) + %w[id price] end - def self.ransackable_associations(auth_object = nil) - %w(order group_order_articles) + def self.ransackable_associations(_auth_object = nil) + %w[order group_order_articles] end # Generate some data for the javascript methods in ordering view def load_data data = {} - data[:account_balance] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.account_balance - data[:available_funds] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.get_available_funds(self) + data[:account_balance] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.account_balance + data[:available_funds] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.get_available_funds(self) # load prices and other stuff.... data[:order_articles] = {} - order.articles_grouped_by_category.each do |article_category, order_articles| + order.articles_grouped_by_category.each do |_article_category, order_articles| order_articles.each do |order_article| # Get the result of last time ordering, if possible goa = group_order_articles.detect { |goa| goa.order_article_id == order_article.id } # Build hash with relevant data data[:order_articles][order_article.id] = { - :price => order_article.article.fc_price, - :unit => order_article.article.unit_quantity, - :quantity => (goa ? goa.quantity : 0), - :others_quantity => order_article.quantity - (goa ? goa.quantity : 0), - :used_quantity => (goa ? goa.result(:quantity) : 0), - :tolerance => (goa ? goa.tolerance : 0), - :others_tolerance => order_article.tolerance - (goa ? goa.tolerance : 0), - :used_tolerance => (goa ? goa.result(:tolerance) : 0), - :total_price => (goa ? goa.total_price : 0), - :missing_units => order_article.missing_units, - :quantity_available => (order.stockit? ? order_article.article.quantity_available : 0) + price: order_article.article_version.fc_group_order_price, + unit: order_article.article_version.unit_quantity, + quantity: (goa ? goa.quantity : 0), + others_quantity: order_article.quantity - (goa ? goa.quantity : 0), + used_quantity: (goa ? goa.result(:quantity) : 0), + tolerance: (goa ? goa.tolerance : 0), + others_tolerance: order_article.tolerance - (goa ? goa.tolerance : 0), + used_tolerance: (goa ? goa.result(:tolerance) : 0), + total_price: (goa ? goa.total_price : 0), + missing_units: order_article.missing_units, + ratio_group_order_unit_supplier_unit: order_article.article_version.convert_quantity(1, + order_article.article_version.supplier_order_unit, order_article.article_version.group_order_unit), + quantity_available: (order.stockit? ? order_article.article_version.article.quantity_available : 0), + minimum_order_quantity: if order_article.article_version.minimum_order_quantity + order_article.article_version.convert_quantity( + order_article.article_version.minimum_order_quantity, order_article.article_version.supplier_order_unit, order_article.article_version.group_order_unit + ) + end } end end @@ -69,12 +76,12 @@ def save_group_order_articles # Get ordered quantities and update group_order_articles/_quantities... if group_order_articles_attributes - quantities = group_order_articles_attributes.fetch(order_article.id.to_s, { :quantity => 0, :tolerance => 0 }) - group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i) + quantities = group_order_articles_attributes.fetch(order_article.id.to_s, { quantity: 0, tolerance: 0 }) + group_order_article.update_quantities(quantities[:quantity].to_f, quantities[:tolerance].to_f) end # Also update results for the order_article - logger.debug "[save_group_order_articles] update order_article.results!" + logger.debug '[save_group_order_articles] update order_article.results!' order_article.update_results! end @@ -83,7 +90,7 @@ def save_group_order_articles # Updates the "price" attribute. def update_price! - total = group_order_articles.includes(:order_article => [:article, :article_price]).to_a.sum(&:total_price) + total = group_order_articles.includes(order_article: :article_version).to_a.sum(&:total_price) update_attribute(:price, total) end @@ -97,7 +104,7 @@ def save_ordering! end def ordergroup_name - ordergroup ? ordergroup.name : I18n.t('model.group_order.stock_ordergroup_name', :user => updated_by.try(:name) || '?') + ordergroup ? ordergroup.name : I18n.t('model.group_order.stock_ordergroup_name', user: updated_by.try(:name) || '?') end def total diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb index 8df4fa59..fdec8365 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -6,21 +6,21 @@ class GroupOrderArticle < ApplicationRecord belongs_to :order_article has_many :group_order_article_quantities, dependent: :destroy - validates_presence_of :group_order, :order_article - validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order + validates :group_order, :order_article, presence: true + validates :order_article_id, uniqueness: { scope: :group_order_id } # just once an article per group order validate :check_order_not_closed # don't allow changes to closed (aka settled) orders - validates :quantity, :tolerance, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + validates :quantity, :tolerance, numericality: { greater_than_or_equal_to: 0 } - scope :ordered, -> { includes(:group_order => :ordergroup).order('groups.name') } + scope :ordered, -> { includes(group_order: :ordergroup).order('groups.name') } localize_input_of :result - def self.ransackable_attributes(auth_object = nil) - %w(id quantity tolerance result) + def self.ransackable_attributes(_auth_object = nil) + %w[id quantity tolerance result] end - def self.ransackable_associations(auth_object = nil) - %w(order_article group_order) + def self.ransackable_associations(_auth_object = nil) + %w[order_article group_order] end # Setter used in group_order_article#new @@ -30,7 +30,7 @@ def ordergroup_id=(id) end def ordergroup_id - group_order.try!(:ordergroup_id) + group_order&.ordergroup_id end # Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties @@ -43,7 +43,7 @@ def update_quantities(quantity, tolerance) # When quantity and tolerance are zero, we don't serve any purpose if quantity == 0 && tolerance == 0 - logger.debug("Self-destructing since requested quantity and tolerance are zero") + logger.debug('Self-destructing since requested quantity and tolerance are zero') destroy! return end @@ -52,24 +52,26 @@ def update_quantities(quantity, tolerance) quantities = group_order_article_quantities.order('created_on DESC').to_a logger.debug("GroupOrderArticleQuantity items found: #{quantities.size}") - if (quantities.size == 0) + if quantities.size == 0 # There is no GroupOrderArticleQuantity item yet, just insert with desired quantities... - logger.debug("No quantities entry at all, inserting a new one with the desired quantities") - quantities.push(GroupOrderArticleQuantity.new(:group_order_article => self, :quantity => quantity, :tolerance => tolerance)) - self.quantity, self.tolerance = quantity, tolerance + logger.debug('No quantities entry at all, inserting a new one with the desired quantities') + quantities.push(GroupOrderArticleQuantity.new(group_order_article: self, quantity: quantity, + tolerance: tolerance)) + self.quantity = quantity + self.tolerance = tolerance else # Decrease quantity/tolerance if necessary by going through the existing items and decreasing their values... i = 0 - while (i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance)) + while i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance) logger.debug("Need to decrease quantities for GroupOrderArticleQuantity[#{quantities[i].id}]") - if (quantity < self.quantity && quantities[i].quantity > 0) + if quantity < self.quantity && quantities[i].quantity > 0 delta = self.quantity - quantity delta = (delta > quantities[i].quantity ? quantities[i].quantity : delta) logger.debug("Decreasing quantity by #{delta}") quantities[i].quantity -= delta self.quantity -= delta end - if (tolerance < self.tolerance && quantities[i].tolerance > 0) + if tolerance < self.tolerance && quantities[i].tolerance > 0 delta = self.tolerance - tolerance delta = (delta > quantities[i].tolerance ? quantities[i].tolerance : delta) logger.debug("Decreasing tolerance by #{delta}") @@ -79,12 +81,12 @@ def update_quantities(quantity, tolerance) i += 1 end # If there is at least one increased value: insert a new GroupOrderArticleQuantity object - if (quantity > self.quantity || tolerance > self.tolerance) - logger.debug("Inserting a new GroupOrderArticleQuantity") + if quantity > self.quantity || tolerance > self.tolerance + logger.debug('Inserting a new GroupOrderArticleQuantity') quantities.insert(0, GroupOrderArticleQuantity.new( - :group_order_article => self, - :quantity => (quantity > self.quantity ? quantity - self.quantity : 0), - :tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0) + group_order_article: self, + quantity: (quantity > self.quantity ? quantity - self.quantity : 0), + tolerance: (tolerance > self.tolerance ? tolerance - self.tolerance : 0) )) # Recalc totals: self.quantity += quantities[0].quantity @@ -93,8 +95,10 @@ def update_quantities(quantity, tolerance) end # Check if something went terribly wrong and quantites have not been adjusted as desired. - if (self.quantity != quantity || self.tolerance != tolerance) - raise ActiveRecord::RecordNotSaved.new('Unable to update GroupOrderArticle/-Quantities to desired quantities!', self) + if self.quantity != quantity || self.tolerance != tolerance + raise ActiveRecord::RecordNotSaved.new( + "Unable to update GroupOrderArticle/-Quantities to desired quantities! (#{self.quantity} != #{quantity} || #{self.tolerance} != {tolerance})", self + ) end # Remove zero-only items. @@ -119,14 +123,15 @@ def calculate_result(total = nil) quantity = tolerance = total_quantity = 0 # Get total - if not total.nil? - logger.debug "<#{order_article.article.name}> => #{total} (given)" - elsif order_article.article.is_a?(StockArticle) - total = order_article.article.quantity - logger.debug "<#{order_article.article.name}> (stock) => #{total}" + if !total.nil? + logger.debug "<#{order_article.article_version.name}> => #{total} (given)" + elsif order_article.article_version.is_a?(StockArticle) + total = order_article.article_version.quantity + logger.debug "<#{order_article.article_version.name}> (stock) => #{total}" else - total = order_article.units_to_order * order_article.price.unit_quantity - logger.debug "<#{order_article.article.name}> units_to_order #{order_article.units_to_order} => #{total}" + total = order_article.article_version.convert_quantity(order_article.units_to_order, + order_article.article_version.supplier_order_unit, order_article.article_version.group_order_unit) + logger.debug "<#{order_article.article_version.name}> units_to_order #{order_article.units_to_order} => #{total}" end if total > 0 @@ -143,7 +148,7 @@ def calculate_result(total = nil) q = goaq.quantity q = [q, total - total_quantity].min if first_order_first_serve total_quantity += q - if goaq.group_order_article_id == self.id + if goaq.group_order_article_id == id logger.debug "increasing quantity by #{q}" quantity += q end @@ -152,11 +157,11 @@ def calculate_result(total = nil) # Determine tolerance to be ordered... if total_quantity < total - logger.debug "determining additional items to be ordered from tolerance" + logger.debug 'determining additional items to be ordered from tolerance' order_quantities.each do |goaq| q = [goaq.tolerance, total - total_quantity].min total_quantity += q - if goaq.group_order_article_id == self.id + if goaq.group_order_article_id == id logger.debug "increasing tolerance by #{q}" tolerance += q end @@ -168,7 +173,7 @@ def calculate_result(total = nil) end # memoize result unless a total is given - r = { :quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance } + r = { quantity: quantity, tolerance: tolerance, total: quantity + tolerance } @calculate_result = r if total.nil? r end @@ -183,8 +188,8 @@ def result(type = :total) # This is used for automatic distribution, e.g., in order.finish! or when receiving orders def save_results!(article_total = nil) new_result = calculate_result(article_total)[:total] - self.update_attribute(:result_computed, new_result) - self.update_attribute(:result, new_result) + update_attribute(:result_computed, new_result) + update_attribute(:result, new_result) end # Returns total price for this individual article @@ -192,14 +197,15 @@ def save_results!(article_total = nil) # the minimum price depending on configuration. When the order is finished it # will be the value depending of the article results. def total_price(order_article = self.order_article) + group_order_price = order_article.article_version.fc_group_order_price if order_article.order.open? if FoodsoftConfig[:tolerance_is_costly] - order_article.article.fc_price * (quantity + tolerance) + group_order_price * (quantity + tolerance) else - order_article.article.fc_price * quantity + group_order_price * quantity end else - order_article.price.fc_price * result + group_order_price * result end end @@ -211,8 +217,8 @@ def result_manually_changed? private def check_order_not_closed - if order_article.order.closed? - errors.add(:order_article, I18n.t('model.group_order_article.order_closed')) - end + return unless order_article.order.closed? + + errors.add(:order_article, I18n.t('model.group_order_article.order_closed')) end end diff --git a/app/models/group_order_article_quantity.rb b/app/models/group_order_article_quantity.rb index 1e29985f..12832b2c 100644 --- a/app/models/group_order_article_quantity.rb +++ b/app/models/group_order_article_quantity.rb @@ -4,5 +4,5 @@ class GroupOrderArticleQuantity < ApplicationRecord belongs_to :group_order_article - validates_presence_of :group_order_article_id + validates :group_order_article_id, presence: true end diff --git a/app/models/invite.rb b/app/models/invite.rb index e37a8a18..d471aa50 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -5,12 +5,12 @@ class Invite < ApplicationRecord belongs_to :user belongs_to :group - validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i - validates_presence_of :user - validates_presence_of :group - validates_presence_of :token - validates_presence_of :expires_at - validate :email_not_already_registered, :on => :create + validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i } + validates :user, presence: true + validates :group, presence: true + validates :token, presence: true + validates :expires_at, presence: true + validate :email_not_already_registered, on: :create before_validation :set_token_and_expires_at @@ -19,15 +19,15 @@ class Invite < ApplicationRecord # Before validation, set token and expires_at. def set_token_and_expires_at self.token = Digest::SHA1.hexdigest(Time.now.to_s + rand(100).to_s) - self.expires_at = Time.now.advance(:days => 7) + self.expires_at = Time.now.advance(days: 7) end private # Custom validation: check that email does not already belong to a registered user. def email_not_already_registered - unless User.find_by_email(self.email).nil? - errors.add(:email, I18n.t('invites.errors.already_member')) - end + return if User.find_by_email(email).nil? + + errors.add(:email, I18n.t('invites.errors.already_member')) end end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 8a9dde9f..c379e15e 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -2,13 +2,13 @@ class Invoice < ApplicationRecord include CustomFields belongs_to :supplier - belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_user_id' + belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id' belongs_to :financial_link, optional: true has_many :deliveries, dependent: :nullify has_many :orders, dependent: :nullify - validates_presence_of :supplier_id - validates_numericality_of :amount, :deposit, :deposit_credit + validates :supplier_id, presence: true + validates :amount, :deposit, :deposit_credit, numericality: true validate :valid_attachment scope :unpaid, -> { where(paid_on: nil) } @@ -22,18 +22,18 @@ class Invoice < ApplicationRecord def attachment=(incoming_file) self.attachment_data = incoming_file.read # allow to soft-fail when FileMagic isn't present and removed from Gemfile (e.g. Heroku) - self.attachment_mime = defined?(FileMagic) ? FileMagic.new(FileMagic::MAGIC_MIME).buffer(self.attachment_data) : 'application/octet-stream' + self.attachment_mime = defined?(FileMagic) ? FileMagic.new(FileMagic::MAGIC_MIME).buffer(attachment_data) : 'application/octet-stream' end def delete_attachment=(value) - if value == '1' - self.attachment_data = nil - self.attachment_mime = nil - end + return unless value == '1' + + self.attachment_data = nil + self.attachment_mime = nil end def user_can_edit?(user) - user.role_finance? || (user.role_invoices? && !self.paid_on && self.created_by.try(:id) == user.id) + user.role_finance? || (user.role_invoices? && !paid_on && created_by.try(:id) == user.id) end # Amount without deposit @@ -43,10 +43,10 @@ def net_amount def orders_sum orders - .joins(order_articles: [:article_price]) - .sum("COALESCE(order_articles.units_received, order_articles.units_billed, order_articles.units_to_order)" \ - + "* article_prices.unit_quantity" \ - + "* ROUND((article_prices.price + article_prices.deposit) * (100 + article_prices.tax) / 100, 2)") + .joins(order_articles: [:article_version]) + .sum('COALESCE(order_articles.units_received, order_articles.units_billed, order_articles.units_to_order)' \ + + '* article_versions.unit_quantity' \ + + '* ROUND((article_versions.price + article_versions.deposit) * (100 + article_versions.tax) / 100, 2)') end def orders_transport_sum @@ -62,11 +62,11 @@ def expected_amount protected def valid_attachment - if attachment_data - mime = MIME::Type.simplified(attachment_mime) - unless ['application/pdf', 'image/jpeg'].include? mime - errors.add :attachment, I18n.t('model.invoice.invalid_mime', :mime => mime) - end - end + return unless attachment_data + + mime = MIME::Type.simplified(attachment_mime) + return if ['application/pdf', 'image/jpeg'].include? mime + + errors.add :attachment, I18n.t('model.invoice.invalid_mime', mime: mime) end end diff --git a/app/models/membership.rb b/app/models/membership.rb index bebf00e2..4ebc061c 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -8,6 +8,6 @@ class Membership < ApplicationRecord # check if this is the last admin-membership and deny def check_last_admin - raise I18n.t('model.membership.no_admin_delete') if self.group.role_admin? && self.group.memberships.size == 1 && Group.where(role_admin: true).count == 1 + raise I18n.t('model.membership.no_admin_delete') if group.role_admin? && group.memberships.size == 1 && Group.where(role_admin: true).count == 1 end end diff --git a/app/models/order.rb b/app/models/order.rb index 15e209d1..db17e345 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -2,29 +2,30 @@ class Order < ApplicationRecord attr_accessor :ignore_warnings, :transport_distribution # Associations - has_many :order_articles, :dependent => :destroy - has_many :articles, :through => :order_articles - has_many :group_orders, :dependent => :destroy - has_many :ordergroups, :through => :group_orders - has_many :users_ordered, :through => :ordergroups, :source => :users - has_many :comments, -> { order('created_at') }, :class_name => "OrderComment" + has_many :order_articles, dependent: :destroy + has_many :article_versions, through: :order_articles + has_many :articles, through: :article_versions + has_many :group_orders, dependent: :destroy + has_many :ordergroups, through: :group_orders + has_many :users_ordered, through: :ordergroups, source: :users + has_many :comments, -> { order('created_at') }, class_name: 'OrderComment' has_many :stock_changes belongs_to :invoice, optional: true belongs_to :supplier, optional: true - belongs_to :updated_by, :class_name => 'User', :foreign_key => 'updated_by_user_id' - belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_user_id' + belongs_to :updated_by, class_name: 'User', foreign_key: 'updated_by_user_id' + belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id' enum end_action: { no_end_action: 0, auto_close: 1, auto_close_and_send: 2, auto_close_and_send_min_quantity: 3 } - enum transport_distribution: [:skip, :ordergroup, :price, :articles] + enum transport_distribution: { skip: 0, ordergroup: 1, price: 2, articles: 3 } # Validations - validates_presence_of :starts + validates :starts, presence: true validate :starts_before_ends, :include_articles validate :keep_ordered_articles + before_validation :distribute_transport # Callbacks after_save :save_order_articles, :update_price_of_group_orders! - before_validation :distribute_transport # Finders scope :started, -> { where('starts <= ?', Time.now) } @@ -49,12 +50,12 @@ class Order < ApplicationRecord include DateTimeAttributeValidate date_time_attribute :starts, :boxfill, :ends - def self.ransackable_attributes(auth_object = nil) - %w(id state supplier_id starts boxfill ends pickup) + def self.ransackable_attributes(_auth_object = nil) + %w[id state supplier_id starts boxfill ends pickup] end - def self.ransackable_associations(auth_object = nil) - %w(supplier articles order_articles) + def self.ransackable_associations(_auth_object = nil) + %w[supplier articles order_articles] end def stockit? @@ -69,10 +70,10 @@ def articles_for_ordering if stockit? # make sure to include those articles which are no longer available # but which have already been ordered in this stock order - StockArticle.available.includes(:article_category) - .order('article_categories.name', 'articles.name').reject { |a| + StockArticle.available.includes(latest_article_version: :article_category) + .order('article_categories.name', 'article_versions.name').reject do |a| a.quantity_available <= 0 && !a.ordered_in_order?(self) - }.group_by { |a| a.article_category.name } + end.group_by { |a| a.article_category.name } else supplier.articles.available.group_by { |a| a.article_category.name } end @@ -87,12 +88,10 @@ def supplier_articles end # Save ids, and create/delete order_articles after successfully saved the order - def article_ids=(ids) - @article_ids = ids - end + attr_writer :article_ids def article_ids - @article_ids ||= order_articles.map { |a| a.article_id.to_s } + @article_ids ||= order_articles.map { |oa| oa.article_version.article_id.to_s } end # Returns an array of article ids that lead to a validation error. @@ -101,19 +100,19 @@ def erroneous_article_ids end def open? - state == "open" + state == 'open' end def finished? - state == "finished" || state == "received" + state == 'finished' || state == 'received' end def received? - state == "received" + state == 'received' end def closed? - state == "closed" + state == 'closed' end def boxfill? @@ -134,11 +133,18 @@ def init_dates self.starts ||= Time.now if FoodsoftConfig[:order_schedule] # try to be smart when picking a reference day - last = (DateTime.parse(FoodsoftConfig[:order_schedule][:initial]) rescue nil) + last = begin + DateTime.parse(FoodsoftConfig[:order_schedule][:initial]) + rescue StandardError + nil + end last ||= Order.finished.reorder(:starts).first.try(:starts) last ||= self.starts # adjust boxfill and end date - self.boxfill ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:boxfill] if is_boxfill_useful? + if is_boxfill_useful? + self.boxfill ||= FoodsoftDateUtil.next_occurrence last, self.starts, + FoodsoftConfig[:order_schedule][:boxfill] + end self.ends ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:ends] end self @@ -149,7 +155,7 @@ def init_dates def self.ordergroup_group_orders_map(ordergroup) orders = includes(:supplier) group_orders = GroupOrder.where(ordergroup_id: ordergroup.id, order_id: orders.map(&:id)) - group_orders_hash = Hash[group_orders.collect { |go| [go.order_id, go] }] + group_orders_hash = group_orders.index_by { |go| go.order_id } orders.map do |order| { order: order, @@ -160,11 +166,11 @@ def self.ordergroup_group_orders_map(ordergroup) # search GroupOrder of given Ordergroup def group_order(ordergroup) - group_orders.where(:ordergroup_id => ordergroup.id).first + group_orders.where(ordergroup_id: ordergroup.id).first end def stock_group_order - group_orders.where(:ordergroup_id => nil).first + group_orders.where(ordergroup_id: nil).first end # Returns OrderArticles in a nested Array, grouped by category and ordered by article name. @@ -172,9 +178,10 @@ def stock_group_order # e.g: [["drugs",[teethpaste, toiletpaper]], ["fruits" => [apple, banana, lemon]]] def articles_grouped_by_category @articles_grouped_by_category ||= order_articles - .includes([:article_price, :group_order_articles, :article => :article_category]) - .order('articles.name') - .group_by { |a| a.article.article_category.name } + .includes([:article_version, :group_order_articles, + { article_version: :article_category }]) + .order('article_versions.name') + .group_by { |oa| oa.article_version.article_category.name } .sort { |a, b| a[0] <=> b[0] } end @@ -189,10 +196,10 @@ def articles_sort_by_category # FIXME: Consider order.foodcoop_result def profit(options = {}) markup = options[:without_markup] || false - if invoice - groups_sum = markup ? sum(:groups_without_markup) : sum(:groups) - groups_sum - invoice.net_amount - end + return unless invoice + + groups_sum = markup ? sum(:groups_without_markup) : sum(:groups) + groups_sum - invoice.net_amount end # Returns the all round price of a finished order @@ -202,26 +209,27 @@ def profit(options = {}) # :fc, guess what... def sum(type = :gross) total = 0 - if type == :net || type == :gross || type == :fc - for oa in order_articles.ordered.includes(:article, :article_price) - quantity = oa.units * oa.price.unit_quantity + if %i[net gross fc].include?(type) + for oa in order_articles.ordered.includes(:article_version) + quantity = oa.units * oa.article_version.convert_quantity(1, oa.article_version.supplier_order_unit, + oa.article_version.group_order_unit) case type when :net - total += quantity * oa.price.price + total += quantity * oa.article_version.group_order_price when :gross - total += quantity * oa.price.gross_price + total += quantity * oa.article_version.gross_group_order_price when :fc - total += quantity * oa.price.fc_price + total += quantity * oa.article_version.fc_group_order_price end end - elsif type == :groups || type == :groups_without_markup - for go in group_orders.includes(group_order_articles: { order_article: [:article, :article_price] }) + elsif %i[groups groups_without_markup].include?(type) + for go in group_orders.includes(group_order_articles: { order_article: :article_version }) for goa in go.group_order_articles case type when :groups - total += goa.result * goa.order_article.price.fc_price + total += goa.result * goa.order_article.article_version.fc_group_order_price when :groups_without_markup - total += goa.result * goa.order_article.price.gross_price + total += goa.result * goa.order_article.article_version.gross_group_order_price end end end @@ -232,36 +240,29 @@ def sum(type = :gross) # Finishes this order. This will set the order state to "finish" and the end property to the current time. # Ignored if the order is already finished. def finish!(user) - unless finished? - Order.transaction do - # set new order state (needed by notify_order_finished) - update_attributes!(:state => 'finished', :ends => Time.now, :updated_by => user) - - # Update order_articles. Save the current article_price to keep price consistency - # Also save results for each group_order_result - # Clean up - order_articles.includes(:article).each do |oa| - oa.update_attribute(:article_price, oa.article.article_prices.first) - oa.group_order_articles.each do |goa| - goa.save_results! - # Delete no longer required order-history (group_order_article_quantities) and - # TODO: Do we need articles, which aren't ordered? (units_to_order == 0 ?) - # A: Yes, we do - for redistributing articles when the number of articles - # delivered changes, and for statistics on popular articles. Records - # with both tolerance and quantity zero can be deleted. - # goa.group_order_article_quantities.clear - end + return if finished? + + Order.transaction do + # set new order state (needed by notify_order_finished) + update_attributes!(state: 'finished', ends: Time.now, updated_by: user) + + # Update order_articles. Save the current article_version to keep price consistency + # Also save results for each group_order_result + # Clean up + order_articles.each do |oa| + oa.group_order_articles.each do |goa| + goa.save_results! end + end - # Update GroupOrder prices - group_orders.each(&:update_price!) + # Update GroupOrder prices + group_orders.each(&:update_price!) - # Stats - ordergroups.each(&:update_stats!) + # Stats + ordergroups.each(&:update_stats!) - # Notifications - NotifyFinishedOrderJob.perform_later(self) - end + # Notifications + NotifyFinishedOrderJob.perform_later(self) end end @@ -275,13 +276,13 @@ def close!(user, transaction_type = nil) charge_group_orders!(user, transaction_type) if stockit? # Decreases the quantity of stock_articles - for oa in order_articles.includes(:article) + for oa in order_articles.includes(article_version: :article) oa.update_results! # Update units_to_order of order_article - stock_changes.create! :stock_article => oa.article, :quantity => oa.units_to_order * -1 + stock_changes.create! stock_article: oa.article_version.article, quantity: oa.units_to_order * -1 end end - self.update_attributes! :state => 'closed', :updated_by => user, :foodcoop_result => profit + update_attributes! state: 'closed', updated_by: user, foodcoop_result: profit end end @@ -289,7 +290,10 @@ def close!(user, transaction_type = nil) def close_direct!(user) raise I18n.t('orders.model.error_closed') if closed? - comments.create(user: user, text: I18n.t('orders.model.close_direct_message')) unless FoodsoftConfig[:charge_members_manually] + unless FoodsoftConfig[:charge_members_manually] + comments.create(user: user, + text: I18n.t('orders.model.close_direct_message')) + end update_attributes! state: 'closed', updated_by: user end @@ -313,13 +317,12 @@ def do_end_action! end def self.finish_ended! - orders = Order.where.not(end_action: Order.end_actions[:no_end_action]).where(state: 'open').where('ends <= ?', DateTime.now) + orders = Order.where.not(end_action: Order.end_actions[:no_end_action]).where(state: 'open').where('ends <= ?', + DateTime.now) orders.each do |order| - begin - order.do_end_action! - rescue => error - ExceptionNotifier.notify_exception(error, data: { foodcoop: FoodsoftConfig.scope, order_id: order.id }) - end + order.do_end_action! + rescue StandardError => e + ExceptionNotifier.notify_exception(e, data: { foodcoop: FoodsoftConfig.scope, order_id: order.id }) end end @@ -329,7 +332,10 @@ def starts_before_ends delta = Rails.env.test? ? 1 : 0 # since Rails 4.2 tests appear to have time differences, with this validation failing errors.add(:ends, I18n.t('orders.model.error_starts_before_ends')) if ends && starts && ends <= (starts - delta) errors.add(:ends, I18n.t('orders.model.error_boxfill_before_ends')) if ends && boxfill && ends <= (boxfill - delta) - errors.add(:boxfill, I18n.t('orders.model.error_starts_before_boxfill')) if boxfill && starts && boxfill <= (starts - delta) + return unless boxfill && starts && boxfill <= (starts - delta) + + errors.add(:boxfill, + I18n.t('orders.model.error_starts_before_boxfill')) end def include_articles @@ -337,23 +343,24 @@ def include_articles end def keep_ordered_articles - chosen_order_articles = order_articles.where(article_id: article_ids) + chosen_order_articles = order_articles.joins(:article_version).where(article_versions: { article_id: article_ids }) to_be_removed = order_articles - chosen_order_articles to_be_removed_but_ordered = to_be_removed.select { |a| a.quantity > 0 || a.tolerance > 0 } - unless to_be_removed_but_ordered.empty? || ignore_warnings - errors.add(:articles, I18n.t(stockit? ? 'orders.model.warning_ordered_stock' : 'orders.model.warning_ordered')) - @erroneous_article_ids = to_be_removed_but_ordered.map { |a| a.article_id } - end + return if to_be_removed_but_ordered.empty? || ignore_warnings + + errors.add(:articles, I18n.t(stockit? ? 'orders.model.warning_ordered_stock' : 'orders.model.warning_ordered')) + @erroneous_article_ids = to_be_removed_but_ordered.map { |oa| oa.article_version.article_id } end def save_order_articles # fetch selected articles articles_list = Article.find(article_ids) # create new order_articles - (articles_list - articles).each { |article| order_articles.create(:article => article) } + articles = article_versions.map(&:article) + (articles_list - articles).each { |article| order_articles.create(article_version: article.latest_article_version) } # delete old order_articles articles.reject { |article| articles_list.include?(article) }.each do |article| - order_articles.detect { |order_article| order_article.article_id == article.id }.destroy + order_articles.detect { |order_article| order_article.article_version.article_id == article.id }.destroy end end @@ -363,17 +370,17 @@ def distribute_transport return unless group_orders.any? case transport_distribution.try(&:to_i) - when Order.transport_distributions[:ordergroup] then + when Order.transport_distributions[:ordergroup] amount = transport / group_orders.size group_orders.each do |go| go.transport = amount.ceil(2) end - when Order.transport_distributions[:price] then + when Order.transport_distributions[:price] amount = transport / group_orders.sum(:price) group_orders.each do |go| go.transport = (amount * go.price).ceil(2) end - when Order.transport_distributions[:articles] then + when Order.transport_distributions[:articles] amount = transport / group_orders.includes(:group_order_articles).sum(:result) group_orders.each do |go| go.transport = (amount * go.group_order_articles.sum(:result)).ceil(2) diff --git a/app/models/order_article.rb b/app/models/order_article.rb index 38f353ab..2c593c8f 100644 --- a/app/models/order_article.rb +++ b/app/models/order_article.rb @@ -5,33 +5,36 @@ class OrderArticle < ApplicationRecord attr_reader :update_global_price belongs_to :order - belongs_to :article - belongs_to :article_price, optional: true - has_many :group_order_articles, :dependent => :destroy + belongs_to :article_version + has_many :group_order_articles, dependent: :destroy - validates_presence_of :order_id, :article_id - validate :article_and_price_exist - validates_uniqueness_of :article_id, scope: :order_id + validates :order_id, :article_version_id, presence: true + validate :article_version_and_price_exist + validates :article_version_id, uniqueness: { scope: :order_id } - _ordered_sql = "order_articles.units_to_order > 0 OR order_articles.units_billed > 0 OR order_articles.units_received > 0" + _ordered_sql = 'order_articles.units_to_order > 0 OR order_articles.units_billed > 0 OR order_articles.units_received > 0' scope :ordered, -> { where(_ordered_sql) } - scope :ordered_or_member, -> { includes(:group_order_articles).where("#{_ordered_sql} OR order_articles.quantity > 0 OR group_order_articles.result > 0") } + scope :ordered_or_member, lambda { + includes(:group_order_articles).where("#{_ordered_sql} OR order_articles.quantity > 0 OR group_order_articles.result > 0") + } + scope :belonging_to_open_order, -> { joins(:order).merge(Order.open) } + scope :belonging_to_finished_order, -> { joins(:order).merge(Order.finished) } before_create :init_from_balancing after_destroy :update_ordergroup_prices - def self.ransackable_attributes(auth_object = nil) - %w(id order_id article_id quantity tolerance units_to_order) + def self.ransackable_attributes(_auth_object = nil) + %w[id order_id article_id quantity tolerance units_to_order] end - def self.ransackable_associations(auth_object = nil) - %w(order article) + def self.ransackable_associations(_auth_object = nil) + %w[order article] end - # This method returns either the ArticlePrice or the Article + # This method returns either the ArticleVersion or the Article # The first will be set, when the the order is finished def price - article_price || article + article_version || article end # latest information on available units @@ -46,7 +49,7 @@ def units # In balancing this can differ from ordered (by supplier) quantity for this article. def group_orders_sum quantity = group_order_articles.collect(&:result).sum - { :quantity => quantity, :price => quantity * price.fc_price } + { quantity: quantity, price: quantity * price.fc_group_order_price } end # Update quantity/tolerance/units_to_order from group_order_articles @@ -81,32 +84,26 @@ def update_results! # 4 | 5 | 4 | 2 # def calculate_units_to_order(quantity, tolerance = 0) - unit_size = price.unit_quantity - units = quantity / unit_size - remainder = quantity % unit_size - units += ((remainder > 0) && (remainder + tolerance >= unit_size) ? 1 : 0) + return price.minimum_order_quantity if quantity > 0 && !price.minimum_order_quantity.nil? && quantity < price.minimum_order_quantity && quantity + tolerance >= price.minimum_order_quantity + + unit_size = price.convert_quantity(1, price.supplier_order_unit, price.group_order_unit) + if price.supplier_order_unit_is_si_convertible + quantity / unit_size + else + units = (quantity / unit_size).floor + remainder = quantity % unit_size + units += ((remainder > 0) && (remainder + tolerance >= unit_size) ? 1 : 0) + end end # Calculate price for ordered quantity. def total_price - units * price.unit_quantity * price.price + units * price.price end # Calculate gross price for ordered qunatity. def total_gross_price - units * price.unit_quantity * price.gross_price - end - - def ordered_quantities_different_from_group_orders?(ordered_mark = "!", billed_mark = "?", received_mark = "?") - if not units_received.nil? - ((units_received * price.unit_quantity) == group_orders_sum[:quantity]) ? false : received_mark - elsif not units_billed.nil? - ((units_billed * price.unit_quantity) == group_orders_sum[:quantity]) ? false : billed_mark - elsif not units_to_order.nil? - ((units_to_order * price.unit_quantity) == group_orders_sum[:quantity]) ? false : ordered_mark - else - nil # can happen in integration tests - end + units * price.gross_price end # redistribute articles over ordergroups @@ -124,12 +121,16 @@ def redistribute(quantity, surplus = [:tolerance], update_totals = true) if surplus.index(:tolerance).nil? qty_for_members = [qty_left, self.quantity].min else - qty_for_members = [qty_left, self.quantity + self.tolerance].min + qty_for_members = [qty_left, self.quantity + tolerance].min counts[surplus.index(:tolerance)] = [0, qty_for_members - self.quantity].max end # Recompute - group_order_articles.each { |goa| goa.save_results! qty_for_members } + group_order_articles.each do |goa| + group_order_total = article_version.convert_quantity(qty_for_members, article_version.supplier_order_unit, + article_version.group_order_unit) + goa.save_results!(group_order_total) + end qty_left -= qty_for_members # if there's anything left, move to stock if wanted @@ -139,9 +140,7 @@ def redistribute(quantity, surplus = [:tolerance], update_totals = true) # 2) if not found, create new stock article # avoiding duplicate stock article names end - if qty_left > 0 && surplus.index(nil) - counts[surplus.index(nil)] = qty_left - end + counts[surplus.index(nil)] = qty_left if qty_left > 0 && surplus.index(nil) # Update GroupOrder prices & Ordergroup stats # TODO only affected group_orders, and once after redistributing all articles @@ -150,52 +149,42 @@ def redistribute(quantity, surplus = [:tolerance], update_totals = true) order.ordergroups.each(&:update_stats!) end - # TODO notifications + # TODO: notifications counts end # Updates order_article and belongings during balancing process - def update_article_and_price!(order_article_attributes, article_attributes, price_attributes = nil) + def update_handling_versioning!(order_article_attributes, version_attributes) OrderArticle.transaction do # Updates self - self.update_attributes!(order_article_attributes) - - # Updates article - article.update_attributes!(article_attributes) - - # Updates article_price belonging to current order article - if price_attributes.present? - article_price.attributes = price_attributes - if article_price.changed? - # Updates also price attributes of article if update_global_price is selected - if update_global_price - article.update_attributes!(price_attributes) - self.article_price = article.article_prices.first and save # Assign new created article price to order article - else - # Creates a new article_price if neccessary - # Set created_at timestamp to order ends, to make sure the current article price isn't changed - create_article_price!(price_attributes.merge(article_id: article_id, created_at: order.ends)) and save - end - - # Updates ordergroup values - update_ordergroup_prices - end + update_attributes!(order_article_attributes) + + # Updates article_version belonging to current order article + original_article_version = article_version.duplicate_including_article_unit_ratios + article_version.assign_attributes(version_attributes) + if article_version.changed? + update_or_create_article_version(version_attributes, original_article_version) + + # Updates ordergroup values + update_ordergroup_prices end end end def update_global_price=(value) - @update_global_price = (value == true || value == '1') ? true : false + @update_global_price = [true, '1'].include?(value) ? true : false end # @return [Number] Units missing for the last +unit_quantity+ of the article. def missing_units - _missing_units(price.unit_quantity, quantity, tolerance) + unit_ratio = price.convert_quantity(1, price.supplier_order_unit, price.group_order_unit) + _missing_units(unit_ratio, quantity, tolerance, price.minimum_order_quantity) end def missing_units_was - _missing_units(price.unit_quantity, quantity_was, tolerance_was) + unit_ratio = price.convert_quantity(1, price.supplier_order_unit, price.group_order_unit) + _missing_units(unit_ratio, quantity_was, tolerance_was, price.minimum_order_quantity) end # Check if the result of any associated GroupOrderArticle was overridden manually @@ -209,16 +198,48 @@ def difference_received_ordered private - def article_and_price_exist - errors.add(:article, I18n.t('model.order_article.error_price')) if !(article = Article.find(article_id)) || article.fc_price.nil? - rescue - errors.add(:article, I18n.t('model.order_article.error_price')) + def article_version_and_price_exist + if !(article_version = ArticleVersion.find(article_version_id)) || article_version.fc_price.nil? + errors.add(:article_version, + I18n.t('model.order_article.error_price')) + end + rescue StandardError + errors.add(:article_version, I18n.t('model.order_article.error_price')) end # Associate with current article price if created in a finished order def init_from_balancing - if order.present? && order.finished? - self.article_price = article.article_prices.first + return unless order.present? && order.finished? + + self.article_version = article_version.article.article_versions.first + end + + def update_or_create_article_version(version_attributes, original_article_version) + version_attributes = version_attributes.merge(article_id: article_version.article_id) + + modifying_earlier_version = article_version.article.latest_article_version.id != article_version_id + finished_order_article_using_same_version = OrderArticle.belonging_to_finished_order.where(article_version_id: article_version_id).where.not(id: id) + + if (!update_global_price && modifying_earlier_version && !finished_order_article_using_same_version.exists?) || + (update_global_price && !modifying_earlier_version) + # update in place: + article_version.save + else + # create new version: + original_version_id = article_version.id + self.article_version = article_version.duplicate_including_article_unit_ratios + article_version.save + update_attribute(:article_version_id, article_version.id) + + if update_global_price + # update open order articles: + OrderArticle.belonging_to_open_order.where(article_version_id: original_version_id).update_all(article_version_id: article_version.id) + else + # create yet *another* version, wich contains the old data, so new orders will continue using that data: + # (The checkbox "Also update the price of future orders" not being checked implies that) + original_article_version.created_at = article_version.created_at + 1.second + original_article_version.save + end end end @@ -241,14 +262,20 @@ def enforce_boxfill unless (delta_q == 0 && delta_t >= 0) || (delta_mis < 0 && delta_box >= 0 && delta_t >= 0) || (delta_q > 0 && delta_q == -delta_t) - raise ActiveRecord::RecordNotSaved.new("Change not acceptable in boxfill phase for '#{article.name}', sorry.", self) + raise ActiveRecord::RecordNotSaved.new("Change not acceptable in boxfill phase for '#{article.name}', sorry.", + self) end end - def _missing_units(unit_quantity, quantity, tolerance) - units = unit_quantity - ((quantity % unit_quantity) + tolerance) + def _missing_units(unit_ratio, quantity, tolerance, minimum_order_quantity) + return minimum_order_quantity - quantity - tolerance if !minimum_order_quantity.nil? && quantity > 0 && quantity + tolerance < minimum_order_quantity + + return 0 if article_version.supplier_order_unit_is_si_convertible + + units = unit_ratio - ((quantity % unit_ratio) + tolerance) + units = 0 if units < 0 - units = 0 if units == unit_quantity + units = 0 if units == unit_ratio units end end diff --git a/app/models/order_comment.rb b/app/models/order_comment.rb index 5f35d98c..b11388b0 100644 --- a/app/models/order_comment.rb +++ b/app/models/order_comment.rb @@ -2,6 +2,6 @@ class OrderComment < ApplicationRecord belongs_to :order belongs_to :user - validates_presence_of :order_id, :user_id, :text - validates_length_of :text, :minimum => 3 + validates :order_id, :user_id, :text, presence: true + validates :text, length: { minimum: 3 } end diff --git a/app/models/ordergroup.rb b/app/models/ordergroup.rb index f699755b..88b01bd4 100644 --- a/app/models/ordergroup.rb +++ b/app/models/ordergroup.rb @@ -15,7 +15,7 @@ class Ordergroup < Group has_many :orders, through: :group_orders has_many :group_order_articles, through: :group_orders - validates_numericality_of :account_balance, :message => I18n.t('ordergroups.model.invalid_balance') + validates :account_balance, numericality: { message: I18n.t('ordergroups.model.invalid_balance') } validate :uniqueness_of_name, :uniqueness_of_members after_create :update_stats! @@ -27,7 +27,7 @@ def contact end def non_members - User.natural_order.all.reject { |u| (users.include?(u) || u.ordergroup) } + User.natural_order.all.reject { |u| users.include?(u) || u.ordergroup } end def self.include_transaction_class_sum @@ -51,9 +51,9 @@ def self.custom_fields def last_user_activity last_active_user = users.order('users.last_activity DESC').first - if last_active_user - last_active_user.last_activity - end + return unless last_active_user + + last_active_user.last_activity end # the most recent order this ordergroup was participating in @@ -86,12 +86,14 @@ def financial_transaction_class_balance(klass) # Throws an exception if it fails. def add_financial_transaction!(amount, note, user, transaction_type, link = nil, group_order = nil) transaction do - t = FinancialTransaction.new(ordergroup: self, amount: amount, note: note, user: user, financial_transaction_type: transaction_type, financial_link: link, group_order: group_order) + t = FinancialTransaction.new(ordergroup: self, amount: amount, note: note, user: user, + financial_transaction_type: transaction_type, financial_link: link, group_order: group_order) t.save! update_balance! # Notify only when order group had a positive balance before the last transaction: - if t.amount < 0 && self.account_balance < 0 && self.account_balance - t.amount >= 0 - NotifyNegativeBalanceJob.perform_later(self, t) + if t.amount < 0 && account_balance < 0 && account_balance - t.amount >= 0 + NotifyNegativeBalanceJob.perform_later(self, + t) end t end @@ -101,10 +103,11 @@ def update_stats! # Get hours for every job of each user in period jobs = users.to_a.sum { |u| u.tasks.done.where('updated_on > ?', APPLE_MONTH_AGO.month.ago).sum(:duration) } # Get group_order.price for every finished order in this period - orders_sum = group_orders.includes(:order).merge(Order.finished).where('orders.ends >= ?', APPLE_MONTH_AGO.month.ago).references(:orders).sum(:price) + orders_sum = group_orders.includes(:order).merge(Order.finished).where('orders.ends >= ?', + APPLE_MONTH_AGO.month.ago).references(:orders).sum(:price) @readonly = false # Dirty hack, avoid getting RecordReadOnly exception when called in task after_save callback. A rails bug? - update_attribute(:stats, { :jobs_size => jobs, :orders_sum => orders_sum }) + update_attribute(:stats, { jobs_size: jobs, orders_sum: orders_sum }) end def update_balance! @@ -116,13 +119,17 @@ def update_balance! end def avg_jobs_per_euro - stats[:jobs_size].to_f / stats[:orders_sum].to_f rescue 0 + stats[:jobs_size].to_f / stats[:orders_sum].to_f + rescue StandardError + 0 end # This is the ordergroup job per euro performance # in comparison to the hole foodcoop average def apples - ((avg_jobs_per_euro / Ordergroup.avg_jobs_per_euro) * 100).to_i rescue 0 + ((avg_jobs_per_euro / Ordergroup.avg_jobs_per_euro) * 100).to_i + rescue StandardError + 0 end # If the the option stop_ordering_under is set, the ordergroup is only allowed to participate in an order, @@ -141,7 +148,11 @@ def not_enough_apples? # Global average def self.avg_jobs_per_euro stats = Ordergroup.pluck(:stats) - stats.sum { |s| s[:jobs_size].to_f } / stats.sum { |s| s[:orders_sum].to_f } rescue 0 + begin + stats.sum { |s| s[:jobs_size].to_f } / stats.sum { |s| s[:orders_sum].to_f } + rescue StandardError + 0 + end end def account_updated @@ -153,17 +164,20 @@ def account_updated # Make sure, that a user can only be in one ordergroup def uniqueness_of_members users.each do |user| - errors.add :user_tokens, I18n.t('ordergroups.model.error_single_group', :user => user.display) if user.groups.where(:type => 'Ordergroup').size > 1 + if user.groups.where(type: 'Ordergroup').size > 1 + errors.add :user_tokens, + I18n.t('ordergroups.model.error_single_group', user: user.display) + end end end # Make sure, the name is uniq, add usefull message if uniq group is already deleted def uniqueness_of_name group = Ordergroup.where(name: name) - group = group.where.not(id: self.id) unless new_record? - if group.exists? - message = group.first.deleted? ? :taken_with_deleted : :taken - errors.add :name, message - end + group = group.where.not(id: id) unless new_record? + return unless group.exists? + + message = group.first.deleted? ? :taken_with_deleted : :taken + errors.add :name, message end end diff --git a/app/models/periodic_task_group.rb b/app/models/periodic_task_group.rb index c0a2b10f..3415bce9 100644 --- a/app/models/periodic_task_group.rb +++ b/app/models/periodic_task_group.rb @@ -5,7 +5,7 @@ def has_next_task? return false if tasks.empty? return false if tasks.first.due_date.nil? - return true + true end def create_next_task @@ -18,15 +18,13 @@ def create_next_task next_task.save self.next_task_date += period_days - self.save + save end def create_tasks_until(create_until) - if has_next_task? - while next_task_date.nil? || next_task_date < create_until - create_next_task - end - end + return unless has_next_task? + + create_next_task while next_task_date.nil? || next_task_date < create_until end def create_tasks_for_upfront_days @@ -53,7 +51,7 @@ def update_tasks_including(template_task, prev_due_date) due_date: task.due_date + due_date_delta) end group_tasks.each do |task| - task.update_columns(periodic_task_group_id: self.id) + task.update_columns(periodic_task_group_id: id) end end diff --git a/app/models/shared_article.rb b/app/models/shared_article.rb deleted file mode 100644 index 238b48f0..00000000 --- a/app/models/shared_article.rb +++ /dev/null @@ -1,26 +0,0 @@ -class SharedArticle < ApplicationRecord - # connect to database from sharedLists-Application - SharedArticle.establish_connection(FoodsoftConfig[:shared_lists]) - # set correct table_name in external DB - self.table_name = 'articles' - - belongs_to :shared_supplier, :foreign_key => :supplier_id - - def build_new_article(supplier) - supplier.articles.build( - :name => name, - :unit => unit, - :note => note, - :manufacturer => manufacturer, - :origin => origin, - :price => price, - :tax => tax, - :deposit => deposit, - :unit_quantity => unit_quantity, - :order_number => number, - :article_category => ArticleCategory.find_match(category), - # convert to db-compatible-string - :shared_updated_on => updated_on.to_formatted_s(:db) - ) - end -end diff --git a/app/models/shared_supplier.rb b/app/models/shared_supplier.rb deleted file mode 100644 index 29c9c1ab..00000000 --- a/app/models/shared_supplier.rb +++ /dev/null @@ -1,33 +0,0 @@ -class SharedSupplier < ApplicationRecord - # connect to database from sharedLists-Application - SharedSupplier.establish_connection(FoodsoftConfig[:shared_lists]) - # set correct table_name in external DB - self.table_name = 'suppliers' - - has_many :suppliers, -> { undeleted } - has_many :shared_articles, :foreign_key => :supplier_id - - def find_article_by_number(order_number) - # note that `shared_articles` uses number instead order_number - cached_articles.detect { |a| a.number == order_number } - end - - def cached_articles - @cached_articles ||= shared_articles.all - end - - # These set of attributes are used to autofill attributes of new supplier, - # when created by import from shared supplier feature. - def autofill_attributes - whitelist = %w(name address phone fax email url delivery_days note) - attributes.select { |k, _v| whitelist.include?(k) } - end - - # return list of synchronisation methods available for this supplier - def shared_sync_methods - methods = [] - methods += %w(all_available all_unavailable) if shared_articles.count < FoodsoftConfig[:shared_supplier_article_sync_limit] - methods += %w(import) - methods - end -end diff --git a/app/models/stock_article.rb b/app/models/stock_article.rb index 42a06d49..30c5b079 100644 --- a/app/models/stock_article.rb +++ b/app/models/stock_article.rb @@ -1,7 +1,7 @@ class StockArticle < Article has_many :stock_changes - scope :available, -> { undeleted.where('quantity > 0') } + scope :available, -> { undeleted.with_latest_versions_and_categories.where('quantity > 0') } validates :quantity, presence: true, numericality: { greater_than_or_equal_to: 0 } @@ -10,11 +10,11 @@ class StockArticle < Article ransack_alias :quantity_available, :quantity # in-line with {StockArticleSerializer} def self.ransackable_attributes(auth_object = nil) - super(auth_object) - %w(supplier_id) + %w(quantity) + super(auth_object) - %w[supplier_id] + %w[quantity] end def self.ransackable_associations(auth_object = nil) - super(auth_object) - %w(supplier) + super(auth_object) - %w[supplier] end # Update the quantity of items in stock @@ -28,8 +28,8 @@ def quantity_available end def quantity_ordered - OrderArticle.where(article_id: id) - .joins(:order).where(orders: { state: %w[open finished received] }).sum(:units_to_order) + OrderArticle.joins(:order, :article_version).where(article_versions: { article_id: id }) + .where(orders: { state: %w[open finished received] }).sum(:units_to_order) end def quantity_history @@ -48,7 +48,7 @@ def mark_as_deleted protected def check_quantity - raise I18n.t('stockit.check.not_empty', :name => name) unless quantity == 0 + raise I18n.t('stockit.check.not_empty', name: name) unless quantity == 0 end # Overwrite Price history of Article. For StockArticles isn't it necessary. diff --git a/app/models/stock_change.rb b/app/models/stock_change.rb index 4cbd8939..03d92c74 100644 --- a/app/models/stock_change.rb +++ b/app/models/stock_change.rb @@ -4,11 +4,11 @@ class StockChange < ApplicationRecord belongs_to :stock_taking, optional: true, foreign_key: 'stock_event_id' belongs_to :stock_article - validates_presence_of :stock_article_id, :quantity - validates_numericality_of :quantity + validates :stock_article_id, :quantity, presence: true + validates :quantity, numericality: true - after_save :update_article_quantity after_destroy :update_article_quantity + after_save :update_article_quantity protected diff --git a/app/models/stock_event.rb b/app/models/stock_event.rb index 4fd82864..7134f7b0 100644 --- a/app/models/stock_event.rb +++ b/app/models/stock_event.rb @@ -2,5 +2,5 @@ class StockEvent < ApplicationRecord has_many :stock_changes, dependent: :destroy has_many :stock_articles, through: :stock_changes - validates_presence_of :date + validates :date, presence: true end diff --git a/app/models/supplier.rb b/app/models/supplier.rb index 862f5c24..f0c750fa 100644 --- a/app/models/supplier.rb +++ b/app/models/supplier.rb @@ -1,33 +1,41 @@ +require 'net/http' + class Supplier < ApplicationRecord include MarkAsDeletedWithName include CustomFields - has_many :articles, -> { where(:type => nil).includes(:article_category).order('article_categories.name', 'articles.name') } - has_many :stock_articles, -> { includes(:article_category).order('article_categories.name', 'articles.name') } + has_many :articles, lambda { + merge(Article.not_in_stock.with_latest_versions_and_categories.order('article_categories.name, article_versions.name')) + } + has_many :stock_articles, lambda { + merge(StockArticle.with_latest_versions_and_categories.order('article_categories.name, article_versions.name')) + } has_many :orders has_many :deliveries has_many :invoices belongs_to :supplier_category - belongs_to :shared_supplier, optional: true # for the sharedLists-App - - validates :name, :presence => true, :length => { :in => 4..30 } - validates :phone, :presence => true, :length => { :in => 8..25 } - validates :address, :presence => true, :length => { :in => 8..50 } - validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true - validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true - validates_length_of :order_howto, :note, maximum: 250 - validate :valid_shared_sync_method + + validates :name, presence: true, length: { in: 4..30 } + validates :phone, presence: true, length: { in: 8..25 } + validates :address, presence: true, length: { in: 8..50 } + validates :iban, format: { with: /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, allow_blank: true } + validates :iban, uniqueness: { case_sensitive: false, allow_blank: true } + validates :order_howto, :note, length: { maximum: 250 } validate :uniqueness_of_name + validates :shared_sync_method, presence: true, unless: -> { supplier_remote_source.blank? } + validates :shared_sync_method, absence: true, if: -> { supplier_remote_source.blank? } + + enum shared_sync_method: { all_available: 'all_available', all_unavailable: 'all_unavailable', import: 'import' } scope :undeleted, -> { where(deleted_at: nil) } scope :having_articles, -> { where(id: Article.undeleted.select(:supplier_id).distinct) } - def self.ransackable_attributes(auth_object = nil) - %w(id name) + def self.ransackable_attributes(_auth_object = nil) + %w[id name] end - def self.ransackable_associations(auth_object = nil) - %w(articles stock_articles orders) + def self.ransackable_associations(_auth_object = nil) + %w[articles stock_articles orders] end # sync all articles with the external database @@ -35,7 +43,9 @@ def self.ransackable_associations(auth_object = nil) # also returns an array with outlisted_articles, which should be deleted # also returns an array with new articles, which should be added (depending on shared_sync_method) def sync_all - updated_article_pairs, outlisted_articles, new_articles = [], [], [] + updated_article_pairs = [] + outlisted_articles = [] + new_articles = [] existing_articles = Set.new for article in articles.undeleted # try to find the associated shared_article @@ -44,19 +54,21 @@ def sync_all if shared_article # article will be updated existing_articles.add(shared_article.id) unequal_attributes = article.shared_article_changed?(self) - unless unequal_attributes.blank? # skip if shared_article has not been changed - article.attributes = unequal_attributes + if unequal_attributes.present? # skip if shared_article has not been changed + article.latest_article_version.article_unit_ratios.each(&:delete) if unequal_attributes.key?(:article_unit_ratios_attributes) + article.latest_article_version.reload + article.latest_article_version.attributes = unequal_attributes updated_article_pairs << [article, unequal_attributes] end # Articles with no order number can be used to put non-shared articles # in a shared supplier, with sync keeping them. - elsif not article.order_number.blank? + elsif article.order_number.present? # article isn't in external database anymore outlisted_articles << article end end # Find any new articles, unless the import is manual - if ['all_available', 'all_unavailable'].include?(shared_sync_method) + if %w[all_available all_unavailable].include?(shared_sync_method) # build new articles shared_supplier .shared_articles @@ -64,10 +76,12 @@ def sync_all .find_each { |new_shared_article| new_articles << new_shared_article.build_new_article(self) } # make them unavailable when desired if shared_sync_method == 'all_unavailable' - new_articles.each { |new_article| new_article.availability = false } + new_articles.each do |new_article| + new_article.latest_article_version.availability = false + end end end - return [updated_article_pairs, outlisted_articles, new_articles] + [updated_article_pairs, outlisted_articles, new_articles] end # Synchronise articles with spreadsheet. @@ -77,46 +91,24 @@ def sync_all # @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet. # @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price. def sync_from_file(file, options = {}) - all_order_numbers = [] - updated_article_pairs, outlisted_articles, new_articles = [], [], [] - FoodsoftFile::parse file, options do |status, new_attrs, line| - article = articles.undeleted.where(order_number: new_attrs[:order_number]).first - new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category]) - new_attrs[:tax] ||= FoodsoftConfig[:tax_default] - new_article = articles.build(new_attrs) - - if status.nil? - if article.nil? - new_articles << new_article - else - unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units)) - unless unequal_attributes.empty? - article.attributes = unequal_attributes - updated_article_pairs << [article, unequal_attributes] - end - end - elsif status == :outlisted && article.present? - outlisted_articles << article + data = FoodsoftFile.parse(file, options) + parse_import_data({ articles: data }, options) + end - # stop when there is a parsing error - elsif status.is_a? String - # @todo move I18n key to model - raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s) - end + def read_from_remote(search_params = {}) + url = URI(supplier_remote_source) + url.query = URI.encode_www_form(search_params) unless search_params.nil? + http = Net::HTTP.new(url.host, url.port) + http.use_ssl = url.scheme == 'https' + request = Net::HTTP::Get.new(url) - all_order_numbers << article.order_number if article - end - if options[:outlist_absent] - outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil]) - end - return [updated_article_pairs, outlisted_articles, new_articles] + response = http.request(request) + JSON.parse(response.body, symbolize_names: true) end - # default value - def shared_sync_method - return unless shared_supplier - - self[:shared_sync_method] || 'import' + def sync_from_remote(options = {}) + data = read_from_remote(options[:search_params]) + parse_import_data(data, options) end def deleted? @@ -136,22 +128,73 @@ def has_tolerance? articles.where('articles.unit_quantity > 1').any? end - protected + # TODO: Maybe use the nilify blanks gem instead of the following two methods?: + def supplier_remote_source=(value) + if value.blank? + self[:supplier_remote_source] = nil + else + super + end + end - # make sure the shared_sync_method is allowed for the shared supplier - def valid_shared_sync_method - if shared_supplier && !shared_supplier.shared_sync_methods.include?(shared_sync_method) - errors.add :shared_sync_method, :included + def shared_sync_method=(value) + if value.blank? + self[:shared_sync_method] = nil + else + super end end + protected + # Make sure, the name is uniq, add usefull message if uniq group is already deleted def uniqueness_of_name supplier = Supplier.where(name: name) - supplier = supplier.where.not(id: self.id) unless new_record? - if supplier.exists? - message = supplier.first.deleted? ? :taken_with_deleted : :taken - errors.add :name, message + supplier = supplier.where.not(id: id) unless new_record? + return unless supplier.exists? + + message = supplier.first.deleted? ? :taken_with_deleted : :taken + errors.add :name, message + end + + def parse_import_data(data, options = {}) + all_order_numbers = [] + updated_article_pairs = [] + outlisted_articles = [] + new_articles = [] + + data[:articles].each do |new_attrs| + article = articles.includes(:latest_article_version).undeleted.where(article_versions: { order_number: new_attrs[:order_number] }).first + new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category]) + new_attrs[:tax] ||= FoodsoftConfig[:tax_default] + new_attrs[:article_unit_ratios] = new_attrs[:article_unit_ratios].map do |ratio_hash| + ArticleUnitRatio.new(ratio_hash) + end + new_article = articles.build + new_article_version = new_article.article_versions.build(new_attrs) + new_article.article_versions << new_article_version + new_article.latest_article_version = new_article_version + + if new_attrs[:availability] + if article.nil? + new_articles << new_article + else + unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units)) + unless unequal_attributes.empty? + article.latest_article_version.article_unit_ratios.target.clear unless unequal_attributes[:article_unit_ratios_attributes].nil? + article.latest_article_version.attributes = unequal_attributes + duped_ratios = article.latest_article_version.article_unit_ratios.map(&:dup) + article.latest_article_version.article_unit_ratios.target.clear + article.latest_article_version.article_unit_ratios.target.push(*duped_ratios) + updated_article_pairs << [article, unequal_attributes] + end + end + elsif article.present? + outlisted_articles << article + end + all_order_numbers << article.order_number if article end + outlisted_articles += articles.includes(:latest_article_version).undeleted.where.not(article_versions: { order_number: all_order_numbers + [nil] }) if options[:outlist_absent] + [updated_article_pairs, outlisted_articles, new_articles] end end diff --git a/app/models/task.rb b/app/models/task.rb index cd748eb3..ead9eb24 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -1,9 +1,9 @@ class Task < ApplicationRecord - has_many :assignments, :dependent => :destroy - has_many :users, :through => :assignments + has_many :assignments, dependent: :destroy + has_many :users, through: :assignments belongs_to :workgroup, optional: true belongs_to :periodic_task_group, optional: true - belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_user_id', optional: true + belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id', optional: true scope :non_group, -> { where(workgroup_id: nil, done: false) } scope :done, -> { where(done: true) } @@ -11,12 +11,12 @@ class Task < ApplicationRecord attr_accessor :current_user_id - validates :name, :presence => true, :length => { :minimum => 3 } - validates :required_users, :presence => true - validates_numericality_of :duration, :required_users, :only_integer => true, :greater_than => 0 - validates_length_of :description, maximum: 250 + validates :name, presence: true, length: { minimum: 3 } + validates :required_users, presence: true + validates :duration, :required_users, numericality: { only_integer: true, greater_than: 0 } + validates :description, length: { maximum: 250 } validates :done, exclusion: { in: [true] }, if: :periodic?, on: :create - validates_presence_of :due_date, if: :periodic? + validates :due_date, presence: { if: :periodic? } before_save :exclude_from_periodic_task_group, if: :changed?, unless: :new_record? after_save :update_ordergroup_stats @@ -35,7 +35,7 @@ def self.accepted_tasks_for(user) # find all tasks in the period (or another number of days) def self.next_assigned_tasks_for(user, number = FoodsoftConfig[:tasks_period_days].to_i) user.tasks.undone.where(assignments: { accepted: true }) - .where(["tasks.due_date >= ? AND tasks.due_date <= ?", Time.now, number.days.from_now]) + .where(['tasks.due_date >= ? AND tasks.due_date <= ?', Time.now, number.days.from_now]) end # count tasks with not enough responsible people @@ -49,7 +49,7 @@ def self.unassigned_tasks_for(user) def self.next_unassigned_tasks_for(user, max = 2) periodic_task_group_count = {} - self.unassigned_tasks_for(user).reject do |item| + unassigned_tasks_for(user).reject do |item| next false unless item.periodic_task_group count = periodic_task_group_count[item.periodic_task_group] || 0 @@ -59,19 +59,19 @@ def self.next_unassigned_tasks_for(user, max = 2) end def periodic? - not periodic_task_group.nil? + !periodic_task_group.nil? end def is_assigned?(user) - self.assignments.detect { |ass| ass.user_id == user.id } + assignments.detect { |ass| ass.user_id == user.id } end def is_accepted?(user) - self.assignments.detect { |ass| ass.user_id == user.id && ass.accepted } + assignments.detect { |ass| ass.user_id == user.id && ass.accepted } end def enough_users_assigned? - assignments.to_a.count(&:accepted) >= required_users ? true : false + assignments.to_a.count(&:accepted) >= required_users end def still_required_users @@ -82,35 +82,31 @@ def still_required_users # and makes the users responsible for the task # TODO: check for maximal number of users def user_list=(ids) - list = ids.split(",").map(&:to_i) + list = ids.split(',').map(&:to_i) new_users = (list - users.collect(&:id)).uniq old_users = users.reject { |user| list.include?(user.id) } self.class.transaction do # delete old assignments - if old_users.any? - assignments.where(user_id: old_users.map(&:id)).each(&:destroy) - end + assignments.where(user_id: old_users.map(&:id)).each(&:destroy) if old_users.any? # create new assignments new_users.each do |id| user = User.find(id) if user.blank? errors.add(:user_list) + elsif id == current_user_id.to_i + assignments.build user: user, accepted: true + # current_user will accept, when he puts himself to the list of users else - if id == current_user_id.to_i - # current_user will accept, when he puts himself to the list of users - self.assignments.build :user => user, :accepted => true - else - # normal assignement - self.assignments.build :user => user - end + # normal assignement + assignments.build user: user end end end end def user_list - @user_list ||= users.collect(&:id).join(", ") + @user_list ||= users.collect(&:id).join(', ') end def update_ordergroup_stats(user_ids = self.user_ids) diff --git a/app/models/user.rb b/app/models/user.rb index 82d80ed5..7dcba174 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,19 +4,19 @@ class User < ApplicationRecord include CustomFields # TODO: acts_as_paraniod ?? - has_many :memberships, :dependent => :destroy - has_many :groups, :through => :memberships + has_many :memberships, dependent: :destroy + has_many :groups, through: :memberships # has_one :ordergroup, :through => :memberships, :source => :group, :class_name => "Ordergroup" def ordergroup - Ordergroup.joins(:memberships).where(memberships: { user_id: self.id }).first + Ordergroup.joins(:memberships).where(memberships: { user_id: id }).first end - has_many :workgroups, :through => :memberships, :source => :group, :class_name => "Workgroup" - has_many :assignments, :dependent => :destroy - has_many :tasks, :through => :assignments - has_many :send_messages, :class_name => "Message", :foreign_key => "sender_id" - has_many :created_orders, :class_name => 'Order', :foreign_key => 'created_by_user_id', :dependent => :nullify - has_many :mail_delivery_status, :class_name => 'MailDeliveryStatus', :foreign_key => 'email', :primary_key => 'email' + has_many :workgroups, through: :memberships, source: :group, class_name: 'Workgroup' + has_many :assignments, dependent: :destroy + has_many :tasks, through: :assignments + has_many :send_messages, class_name: 'Message', foreign_key: 'sender_id' + has_many :created_orders, class_name: 'Order', foreign_key: 'created_by_user_id', dependent: :nullify + has_many :mail_delivery_status, class_name: 'MailDeliveryStatus', foreign_key: 'email', primary_key: 'email' attr_accessor :create_ordergroup, :password, :send_welcome_mail, :settings_attributes @@ -26,22 +26,22 @@ def ordergroup # makes the current_user (logged-in-user) available in models cattr_accessor :current_user - validates_presence_of :email - validates_presence_of :password, :on => :create - validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i - validates_uniqueness_of :email, :case_sensitive => false - validates_presence_of :first_name # for simple_form validations - validates_length_of :first_name, :in => 2..50 - validates_confirmation_of :password - validates_length_of :password, :in => 5..50, :allow_blank => true + validates :email, presence: true + validates :password, presence: { on: :create } + validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i } + validates :email, uniqueness: { case_sensitive: false } + validates :first_name, presence: true # for simple_form validations + validates :first_name, length: { in: 2..50 } + validates :password, confirmation: true + validates :password, length: { in: 5..50, allow_blank: true } # allow nick to be nil depending on foodcoop config # TODO Rails 4 may have a more beautiful way # http://stackoverflow.com/questions/19845910/conditional-allow-nil-part-of-validation - validates_length_of :nick, :in => 2..25, :allow_nil => true, :unless => Proc.new { FoodsoftConfig[:use_nick] } - validates_length_of :nick, :in => 2..25, :allow_nil => false, :if => Proc.new { FoodsoftConfig[:use_nick] } - validates_uniqueness_of :nick, :case_sensitive => false, :allow_nil => true # allow_nil in length validation - validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true - validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true + validates :nick, length: { in: 2..25, allow_nil: true, unless: proc { FoodsoftConfig[:use_nick] } } + validates :nick, length: { in: 2..25, allow_nil: false, if: proc { FoodsoftConfig[:use_nick] } } + validates :nick, uniqueness: { case_sensitive: false, allow_nil: true } # allow_nil in length validation + validates :iban, format: { with: /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, allow_blank: true } + validates :iban, uniqueness: { case_sensitive: false, allow_blank: true } before_validation :set_password after_initialize do @@ -58,17 +58,19 @@ def ordergroup end after_save do - settings_attributes.each do |key, value| - value.each do |k, v| - case v - when '1' - value[k] = true - when '0' - value[k] = false + if settings_attributes + settings_attributes.each do |key, value| + value.each do |k, v| + case v + when '1' + value[k] = true + when '0' + value[k] = false + end end + settings.merge!(key, value) end - self.settings.merge!(key, value) - end if settings_attributes + end if ActiveModel::Type::Boolean.new.cast(create_ordergroup) og = Ordergroup.new({ name: name }) @@ -103,7 +105,7 @@ def self.natural_search(q) match_name = q.split.map do |a| users[:first_name].matches("%#{a}%").or users[:last_name].matches("%#{a}%") end.reduce(:and) - User.where(match_nick.or match_name) + User.where(match_nick.or(match_name)) end def locale @@ -111,7 +113,7 @@ def locale end def name - [first_name, last_name].join(" ") + [first_name, last_name].join(' ') end def receive_email? @@ -120,22 +122,24 @@ def receive_email? # Sets the user's password. It will be stored encrypted along with a random salt. def set_password - unless password.blank? - salt = [Array.new(6) { rand(256).chr }.join].pack("m").chomp - self.password_hash, self.password_salt = Digest::SHA1.hexdigest(password + salt), salt - end + return if password.blank? + + salt = [Array.new(6) { rand(256).chr }.join].pack('m').chomp + self.password_hash = Digest::SHA1.hexdigest(password + salt) + self.password_salt = salt end # Returns true if the password argument matches the user's password. def has_password(password) - Digest::SHA1.hexdigest(password + self.password_salt) == self.password_hash + Digest::SHA1.hexdigest(password + password_salt) == password_hash end # Returns a random password. def new_random_password(size = 6) - c = %w(b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr) - v = %w(a e i o u y) - f, r = true, '' + c = %w[b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr] + v = %w[a e i o u y] + f = true + r = '' (size * 2).times do r << (f ? c[rand * c.size] : v[rand * v.size]) f = !f @@ -198,12 +202,12 @@ def ordergroup_name # returns true if user is a member of a given group def member_of?(group) - group.users.exists?(self.id) + group.users.exists?(id) end # Returns an array with the users groups (but without the Ordergroups -> because tpye=>"") - def member_of_groups() - self.groups.where(type: '') + def member_of_groups + groups.where(type: '') end def deleted? @@ -220,11 +224,9 @@ def restore def self.authenticate(login, password) user = find_by_nick(login) || find_by_email(login) - if user && password && user.has_password(password) - user - else - nil - end + return unless user && password && user.has_password(password) + + user end def self.custom_fields @@ -248,6 +250,6 @@ def display def token_attributes # would be sensible to match ApplicationController#show_user # this should not be part of the model anyway - { :id => id, :name => "#{display} (#{ordergroup.try(:name)})" } + { id: id, name: "#{display} (#{ordergroup.try(:name)})" } end end diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb index bf50c27b..271dec8d 100644 --- a/app/models/workgroup.rb +++ b/app/models/workgroup.rb @@ -3,26 +3,26 @@ class Workgroup < Group has_many :tasks # returns all non-finished tasks - has_many :open_tasks, -> { where(:done => false).order('due_date', 'name') }, :class_name => 'Task' + has_many :open_tasks, -> { where(done: false).order('due_date', 'name') }, class_name: 'Task' - validates_uniqueness_of :name - validate :last_admin_on_earth, :on => :update + validates :name, uniqueness: true + validate :last_admin_on_earth, on: :update before_destroy :check_last_admin_group protected # Check before destroy a group, if this is the last group with admin role def check_last_admin_group - if role_admin && Workgroup.where(role_admin: true).size == 1 - raise I18n.t('workgroups.error_last_admin_group') - end + return unless role_admin && Workgroup.where(role_admin: true).size == 1 + + raise I18n.t('workgroups.error_last_admin_group') end # add validation check on update # Return an error if this is the last group with admin role and role_admin should set to false def last_admin_on_earth - if !role_admin && !Workgroup.where(role_admin: true).where.not(id: id).exists? - errors.add(:role_admin, I18n.t('workgroups.error_last_admin_role')) - end + return unless !role_admin && !Workgroup.where(role_admin: true).where.not(id: id).exists? + + errors.add(:role_admin, I18n.t('workgroups.error_last_admin_role')) end end diff --git a/app/serializers/article_unit_serializer.rb b/app/serializers/article_unit_serializer.rb new file mode 100644 index 00000000..9f1dffc7 --- /dev/null +++ b/app/serializers/article_unit_serializer.rb @@ -0,0 +1,3 @@ +class ArticleUnitSerializer < ActiveModel::Serializer + attributes :id, :unit +end diff --git a/app/serializers/order_article_serializer.rb b/app/serializers/order_article_serializer.rb index 10de058f..033fc577 100644 --- a/app/serializers/order_article_serializer.rb +++ b/app/serializers/order_article_serializer.rb @@ -5,6 +5,6 @@ class OrderArticleSerializer < ActiveModel::Serializer has_one :article def price - object.price.fc_price.to_f + object.article_version.fc_price.to_f end end diff --git a/app/views/article_units/_create_link.html.haml b/app/views/article_units/_create_link.html.haml new file mode 100644 index 00000000..078c5d38 --- /dev/null +++ b/app/views/article_units/_create_link.html.haml @@ -0,0 +1 @@ += link_to t('.add'), article_units_path(unit: unit), class: 'btn btn-mini', remote: true, method: :post, data: {'for-unit': unit} diff --git a/app/views/article_units/_destroy_link.html.haml b/app/views/article_units/_destroy_link.html.haml new file mode 100644 index 00000000..12e1e4e6 --- /dev/null +++ b/app/views/article_units/_destroy_link.html.haml @@ -0,0 +1 @@ +=link_to t('.delete'), article_unit, method: :delete, class: 'btn btn-mini btn-danger', remote: true, data: { confirm: t('.confirm'), 'for-unit': article_unit.unit } diff --git a/app/views/article_units/_rows.html.haml b/app/views/article_units/_rows.html.haml new file mode 100644 index 00000000..8b979205 --- /dev/null +++ b/app/views/article_units/_rows.html.haml @@ -0,0 +1,16 @@ +- @article_units.each do |article_unit| + %tr{class: ("untranslated" if article_unit[:untranslated])} + %td= article_unit[:code] + %td= highlight(article_unit[:symbol], @query) + %td + = highlight(article_unit[:name], @query) + - if article_unit[:untranslated] + %a{title: t('.request_translation'), href: "https://github.com/foodcoops/foodsoft/issues/new?title=Add%20translations%20for%20article%20unit%20%27#{article_unit[:code]}%27", target: '_blank'} + %i.icon-mail-forward + %td= article_unit[:description] + %td + - if article_unit[:record].nil? + = render partial: 'create_link', locals: {unit: article_unit[:code]} + - else + = render partial: 'destroy_link', locals: {article_unit: article_unit[:record]} + diff --git a/app/views/article_units/create.js.haml b/app/views/article_units/create.js.haml new file mode 100644 index 00000000..18eccd4e --- /dev/null +++ b/app/views/article_units/create.js.haml @@ -0,0 +1,2 @@ +$('div.container-fluid').prepend('#{j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.success', name: @available_units[@article_unit.unit][:name])}))}') +$('a[data-for-unit="#{@article_unit.unit}"]').replaceWith('#{j(render(partial: "destroy_link", locals: {article_unit: @article_unit}))}') diff --git a/app/views/article_units/destroy.js.haml b/app/views/article_units/destroy.js.haml new file mode 100644 index 00000000..98d446d0 --- /dev/null +++ b/app/views/article_units/destroy.js.haml @@ -0,0 +1,2 @@ +$('div.container-fluid').prepend('#{j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.success', name: @available_units[@article_unit.unit][:name])}))}') +$('a[data-for-unit="#{@article_unit.unit}"]').replaceWith('#{j(render(partial: "create_link", locals: {unit: @article_unit.unit}))}') diff --git a/app/views/article_units/index.html.haml b/app/views/article_units/index.html.haml new file mode 100644 index 00000000..474880ed --- /dev/null +++ b/app/views/article_units/index.html.haml @@ -0,0 +1,60 @@ +=form_with url: search_article_units_path, method: :get, id: :article_unit_search_form, html: {'data-remote': true} do |form| + .d-flex.align-items-center + = form.text_field :q, id: :article_unit_search, placeholder: t('.search'), class: 'm-0' + = form.check_box :only_recommended, id: :only_recommended, class: 'm-0 ml-2', checked: true + = form.label :only_recommended, t('.only_recommended'), class: 'm-0 ml-1', for: :only_recommended + +%table.table.table-striped + %thead + %tr + %th= heading_helper ArticleUnit, :code, short: true + %th= heading_helper ArticleUnit, :symbol + %th= heading_helper ArticleUnit, :name + %th= heading_helper ArticleUnit, :description + %th + %th + + %tbody#article_units_search_results +%br + + +- content_for :javascript do + :javascript + let timer; + function debounce(timeout, func){ + return (...args) => { + if (timer !== undefined) { + clearTimeout(timer); + timer = undefined; + } + timer = setTimeout(() => { + func.apply(this, args); + }, timeout); + }; + } + + $(document).ready(function () { + $('#article_unit_search, #only_recommended').on('input', debounce(250, () => { + $('#article_unit_search_form').submit(); + })); + }); + + let currentXhr; + + $(document).on('ajax:send', function(_event, xhr) { + if (timer !== undefined) { + clearTimeout(timer); + timer = undefined; + } + if (currentXhr) { + currentXhr.abort(); + } + currentXhr = xhr; + return true; + }); + + $(document).on('ajax:complete', function(_event, _xhr, _status) { + currentXhr = null; + }); + + $('#article_unit_search_form').submit(); diff --git a/app/views/article_units/search.js.haml b/app/views/article_units/search.js.haml new file mode 100644 index 00000000..6ef276a7 --- /dev/null +++ b/app/views/article_units/search.js.haml @@ -0,0 +1 @@ +$('#article_units_search_results').html('#{j(render("rows"))}'); diff --git a/app/views/articles/_article.html.haml b/app/views/articles/_article.html.haml index 33d753d7..35bbbb21 100644 --- a/app/views/articles/_article.html.haml +++ b/app/views/articles/_article.html.haml @@ -3,9 +3,9 @@ %td{'data-check-this' => "#checkbox_#{article.id}", :class => 'click-me'}= article.name %td= article.origin %td= truncate(article.article_category.name, :length => 11) if article.article_category - %td= article.unit + %td= format_group_order_unit_with_ratios(article) %td= truncate(article.note, :length => 11) - %td= article.unit_quantity + %td= format_supplier_order_unit_with_ratios(article) %td{:class => "currency"} %acronym{:title => t('.last_update', last_update: format_date(article.updated_at), gross_price: number_to_currency(article.gross_price))} = number_to_currency(article.price) diff --git a/app/views/articles/_edit_all_table.html.haml b/app/views/articles/_edit_all_table.html.haml index e7752792..58951db7 100644 --- a/app/views/articles/_edit_all_table.html.haml +++ b/app/views/articles/_edit_all_table.html.haml @@ -1,11 +1,21 @@ +- content_for :javascript do + :javascript + const units = #{raw(@all_units.to_json)}; +- content_for :javascript do + = simple_fields_for "articles[TEMPLATEREPLACEMARKER]" do |form| + :javascript + const ratioTemplateHtml = "#{escape_javascript(render(partial: 'shared/article_unit_ratio', locals: {article_unit_ratio: @empty_article_unit_ratio, f: form, article_unit_ratio_counter: -1}))}"; +- price_markup = FoodsoftConfig[:price_markup].to_f += render partial: 'shared/js_templates/unit_conversion_popover_template' %table.table %thead %tr %th= heading_helper Article, :availability, short: true %th= heading_helper Article, :name + - unless @original_units.nil? + %th= heading_helper Article, :original_unit %th= heading_helper Article, :unit %th= heading_helper Article, :price, short: true - %th= heading_helper Article, :unit_quantity, short: true %th= heading_helper Article, :order_number, short: true %th= heading_helper Article, :note %th= heading_helper Article, :article_category @@ -13,18 +23,55 @@ %th= heading_helper Article, :deposit %tbody - @articles.each_with_index do |article, index| - = fields_for "articles[#{article.id || index}]", article do |form| - %tr + %tr{:id => "article_row_#{article.id}"} + = simple_fields_for "articles[#{article.id}]", article.latest_article_version do |form| %td = yield form # allow to add hidden fields to form = form.check_box 'availability' %td= form.text_field 'name', class: 'input-medium' - %td= form.text_field 'unit', class: 'input-mini' - %td= form.text_field 'price', class: 'input-mini' - %td= form.text_field 'unit_quantity', class: 'input-mini' + - unless @original_units.nil? + %td + = "\"#{@original_units[article.id]}\"" unless @original_units[article.id].blank? + %td + .d-flex.gap-1.align-items-center + = form.hidden_field :unit, id: 'article_unit_hidden', value: '' + = form.input :supplier_order_unit, as: :select, collection: @article_units, label: false, value: article.supplier_order_unit, include_blank: t('.custom_unit'), input_html: {class: 'input-medium'} + = form.input :unit, input_html: {class: 'input-mini ml-1'}, label: false + %div.btn-link.toggle-extra-units.text-decoration-none.default-values + %i.icon-cog + %div.extra-unit-fields.form-horizontal + .fold-line + .control-group + %label.control-label{for: 'unit_ratios'} + = "Unit ratios" + %table#fc_base_price{:class => "controls"} + %tbody + - ratios = article.article_unit_ratios + = render :partial => 'shared/article_unit_ratio', :as => 'article_unit_ratio', :collection => ratios, locals: {f: form, original_ratios: article&.article_unit_ratios} + %tfoot + %tr + %td{:colspan => 6} + = link_to t('.add_ratio'), '#', 'data-add-ratio' => true, class: 'btn', title: t(".add_ratio") + .fold-line + = form.input :minimum_order_quantity, label: "Mininum order quantity" do + .input-append + = form.input_field :minimum_order_quantity, class: 'input-mini', title: "total minimum order quantity for this article" + .fold-line + = form.input :billing_unit, as: :select, collection: [], input_html: {'data-initial-value': article.billing_unit, class: 'input-medium'}, include_blank: false + .fold-line + = form.input :group_order_granularity, label: "Allow orders per", input_html: {class: 'input-mini', title: "steps in which ordergroups can order this article"} + = form.input :group_order_unit, as: :select, collection: [], input_html: {'data-initial-value': article.group_order_unit, class: 'input-medium'}, label: '×'.html_safe, include_blank: false + %td + .d-flex.gap-1 + .input-prepend + %span.add-on= t 'number.currency.format.unit' + = form.text_field 'price', class: 'input-mini', style: 'width: 45px' + .input.d-flex.gap-1.control-group + %label=t('articles.form.per') + = form.select :price_unit, [], {include_blank: false}, {class: 'input-medium'} %td= form.text_field 'order_number', class: 'input-mini' %td= form.text_field 'note', class: 'input-medium' - %td= form.collection_select 'article_category_id', ArticleCategory.all, + %td= form.collection_select 'article_category_id', @article_categories, :id, :name, { :include_blank => true }, class: 'input-small' %td.input-append = form.text_field 'tax', class: 'input-mini' @@ -33,3 +80,11 @@ - unless article.errors.empty? %tr.alert %td(colspan="10")= article.errors.full_messages.join(", ") +- content_for :javascript do + - @articles.each_with_index do |article, index| + :javascript + articleUnitRatioTemplate$ = $($.parseHTML(ratioTemplateHtml.replace(/TEMPLATEREPLACEMARKER/g, '#{article.id}'))); + new ArticleForm(articleUnitRatioTemplate$, $('#article_row_#{article.id}'), units, #{price_markup}, $('#article_row_#{article.id}').closest('form'), 'articles_#{article.id}', 'articles[#{article.id}]'); + - if index + 1 === @articles.length + :javascript + $('input[name="commit"]').removeAttr('disabled'); diff --git a/app/views/articles/_form.html.haml b/app/views/articles/_form.html.haml index 7cbd64cd..3238683a 100644 --- a/app/views/articles/_form.html.haml +++ b/app/views/articles/_form.html.haml @@ -1,21 +1,26 @@ -= simple_form_for [@supplier, @article], :validate => true, :remote => true do |f| - = f.hidden_field :shared_updated_on - = f.hidden_field :supplier_id +- url = @article.new_record? ? supplier_articles_path(@supplier, @article, foodcoop: FoodsoftConfig.scope) : supplier_article_path(@supplier, @article) += simple_form_for [@supplier, @article.latest_article_version], :url => url, :validate => true, :remote => true do |f| + :javascript + const articleUnitRatioTemplate$ = $($.parseHTML("#{escape_javascript(render(partial: 'shared/article_unit_ratio', locals: {article_unit_ratio: @empty_article_unit_ratio, f: f, article_unit_ratio_counter: -1}))}")); + const units = #{raw(@all_units.to_json)}; + new ArticleForm(articleUnitRatioTemplate$, $('.article-form').parents('form'), units, #{FoodsoftConfig[:price_markup].to_f}); + + = render partial: 'shared/js_templates/unit_conversion_popover_template' .modal-header = close_button :modal %h3= @article.new_record? ? t('.title_new') : t('.title_edit') - .modal-body + .modal-body.article-form + = f.hidden_field :id = f.input :availability = f.input :name - = render partial: 'shared/article_fields_units', locals: {f: f} + = render partial: 'shared/article_fields_units', locals: {f: f, article: @article} + + = render partial: 'shared/article_fields_price', locals: {f: f, article: @article} = f.input :note = f.association :article_category / TODO labels - - = render partial: 'shared/article_fields_price', locals: {f: f} - = f.input :origin = f.input :manufacturer = f.input :order_number diff --git a/app/views/articles/_import_article_added.js.erb b/app/views/articles/_import_article_added.js.erb deleted file mode 100644 index 6007213f..00000000 --- a/app/views/articles/_import_article_added.js.erb +++ /dev/null @@ -1,4 +0,0 @@ -// update shared article button on create, so that the article will be shown as imported -// reloading the list doesn't work because it breaks paging :( -$('#shared_article_<%= article.shared_article.id %> .actions') - .html('<%= j content_tag(:i, I18n.t('articles.import_search_results.already_imported'), class: 'icon-ok')%>'); diff --git a/app/views/articles/_import_search_results.haml b/app/views/articles/_import_search_results.haml deleted file mode 100644 index c5dd56f2..00000000 --- a/app/views/articles/_import_search_results.haml +++ /dev/null @@ -1,33 +0,0 @@ -- if @articles.empty? - %p= t '.not_found' -- else - = pagination_links_remote @articles - %table.table.table-striped - %thead - %tr - %th= heading_helper Article, :name - %th= heading_helper Article, :origin - %th= heading_helper Article, :manufacturer - %th= heading_helper Article, :note - %th{:style => "width:4em"}= heading_helper Article, :price - %th= heading_helper Article, :unit - %th - %tbody - - for article in @articles - %tr{id: "shared_article_#{article.id}"} - %td= highlight article.name, params.fetch(:name_cont_all_joined, '').split(' ') - %td= article.origin - %td= article.manufacturer - %td{title: article.note}= truncate(article.note, length: 11) - %td= number_to_currency(article.price) - %td - = article.unit - - if article.unit_quantity > 1 - %span{style: 'color: grey'} × #{article.unit_quantity} #{pkg_helper_icon} - %td.actions - - logger.debug "[debug] #{article.attributes.inspect}" - - if @supplier.articles.undeleted.where(order_number: article.number).exists? - %i.icon-ok= t '.already_imported' - - else - = link_to t('.action_import'), import_supplier_articles_path(@supplier, shared_article_id: article.id, direct: true), - remote: true, class: 'btn btn-small btn-success' diff --git a/app/views/articles/_sync.html.haml b/app/views/articles/_sync.html.haml index 05945112..e120e20a 100644 --- a/app/views/articles/_sync.html.haml +++ b/app/views/articles/_sync.html.haml @@ -1,11 +1,15 @@ +- content_for :javascript do + :javascript + const units = #{raw(@all_units.to_json)}; + let articleUnitRatioTemplate$ = undefined; - if @outlisted_articles.any? %h2= t '.outlist.title' %p = t('.outlist.body').html_safe %ul - - for article in @outlisted_articles + - @outlisted_articles.each_with_index do |article, index| %li - = hidden_field_tag "outlisted_articles[#{article.id}]", '1' + = hidden_field_tag "outlisted_articles[#{index}]", article.id = article.name - if article.in_open_order .alert= t '.outlist.alert_used', article: article.name diff --git a/app/views/articles/_sync_table.html.haml b/app/views/articles/_sync_table.html.haml index ac17adfa..0142d636 100644 --- a/app/views/articles/_sync_table.html.haml +++ b/app/views/articles/_sync_table.html.haml @@ -1,18 +1,19 @@ -%table.table +- price_markup = FoodsoftConfig[:price_markup].to_f +%table.table.sync-table %thead %tr %th= heading_helper Article, :name %th= heading_helper Article, :note %th= heading_helper Article, :manufacturer %th= heading_helper Article, :origin - %th= heading_helper Article, :unit - %th= heading_helper Article, :unit_quantity, short: true + %th= heading_helper Article, :supplier_order_unit %th= heading_helper Article, :price %th= heading_helper Article, :tax %th= heading_helper Article, :deposit %th= heading_helper Article, :article_category %tbody - - articles.each do |changed_article, attrs| + - articles.each_with_index do |data, index| + - changed_article, attrs = data - unless changed_article.new_record? - article = Article.find(changed_article.id) %tr{:style => 'color:grey'} @@ -20,27 +21,75 @@ %td= article.note %td= article.manufacturer %td= article.origin - %td= article.unit - %td= article.unit_quantity + %td= ArticleUnitsLib.get_translated_name_for_code(article.supplier_order_unit) %td= number_to_currency article.price %td= number_to_percentage article.tax %td= number_to_currency article.deposit %td= article.article_category.name if article.article_category - %tr - = fields_for "#{field}[]", changed_article do |form| + %tr{:id => "article_row_#{field}_#{index}"} + = render partial: 'shared/js_templates/unit_conversion_popover_template' + = simple_fields_for "#{field}[#{index}]", changed_article.latest_article_version do |form| + - content_for :javascript do + :javascript + articleUnitRatioTemplate$ = $($.parseHTML("#{escape_javascript(render(partial: 'shared/article_unit_ratio', locals: {article_unit_ratio: @empty_article_unit_ratio, f: form, article_unit_ratio_counter: -1}))}")); + new ArticleForm(articleUnitRatioTemplate$, $('#article_row_#{field}_#{index}'), units, #{price_markup}, $('#article_row_#{field}_#{index}').closest('form'), '#{field}_#{index}', '#{field}[#{index}]'); %td{:style => highlight_new(attrs, :name)} = form.text_field 'name', :size => 0 - - hidden_fields.each do |field| - = form.hidden_field field + = form.input :id, as: :hidden unless changed_article.new_record? + -# TODO: + -# - hidden_fields.each do |field| + -# = form.input field, as: :hidden %td{:style => highlight_new(attrs, :note)}= form.text_field 'note', class: 'input-small' %td{:style => highlight_new(attrs, :manufacturer)}= form.text_field 'manufacturer', class: 'input-small' %td{:style => highlight_new(attrs, :origin)}= form.text_field 'origin', class: 'input-mini' - %td{:style => highlight_new(attrs, :unit)}= form.text_field 'unit', class: 'input-mini' - %td{:style => highlight_new(attrs, :unit_quantity)}= form.text_field 'unit_quantity', class: 'input-mini' + %td{:style => highlight_new(attrs, :supplier_order_unit)} + .d-flex.gap-1.align-items-center + = form.input :supplier_order_unit, as: :select, collection: @article_units, label: false, value: changed_article.supplier_order_unit, include_blank: t('.custom_unit'), input_html: {class: 'input-medium'} + = form.input :unit, input_html: {class: 'input-mini ml-1'}, label: false + %div.btn-link.toggle-extra-units.text-decoration-none.default-values + %i.icon-cog{:style => highlight_new(attrs, [:article_unit_ratio_attributes, :minimum_order_quantity, :billing_unit, :group_order_unit, :group_order_granularity])} + %div.extra-unit-fields.form-horizontal + .fold-line + .control-group + %label.control-label{for: 'unit_ratios'} + = "Unit ratios" + %table#fc_base_price{:class => "controls"} + %tbody + - ratios = changed_article.article_unit_ratios + = render :partial => 'shared/article_unit_ratio', :as => 'article_unit_ratio', :collection => ratios, locals: {f: form, original_ratios: article&.article_unit_ratios} + %tfoot + %tr + %td{:colspan => 6} + = link_to t('.add_ratio'), '#', 'data-add-ratio' => true, class: 'btn', title: "add ratio" + - unless changed_article.new_record? + %tr{style: 'color: grey;'} + %td{:colspan => 6} + %ul + - article.article_unit_ratios.each do |ratio| + %li + = "#{ratio.quantity} x #{ArticleUnitsLib.get_translated_name_for_code(ratio.unit)}" + = t 'per' + = ArticleUnitsLib.get_translated_name_for_code(article.supplier_order_unit) + .fold-line + = form.input :minimum_order_quantity, label: "Mininum order quantity" do + .input-append + = form.input_field :minimum_order_quantity, class: 'input-mini', style: highlight_new(attrs, :minimum_order_quantity), title: "total minimum order quantity for this article" + %span.add-on + - unless changed_article.new_record? + %p.help-block{style: 'color: grey;'}=article.minimum_order_quantity.to_s + .fold-line + = form.input :billing_unit, hint: changed_article.new_record? ? nil : ArticleUnitsLib.get_translated_name_for_code(article.billing_unit || article.supplier_order_unit), hint_html: {style: 'color: grey;'}, as: :select, collection: [], input_html: {'data-initial-value': changed_article.billing_unit, class: 'input-medium', style: highlight_new(attrs, :billing_unit)}, include_blank: false + .fold-line + = form.input :group_order_granularity, hint: changed_article.new_record? ? nil : "#{article.group_order_granularity} x #{ArticleUnitsLib.get_translated_name_for_code(article.group_order_unit)}", hint_html: {style: 'color: grey;'}, label: "Allow orders per", input_html: {class: 'input-mini', style: highlight_new(attrs, :group_order_granularity), title: "steps in which ordergroups can order this article"} + = form.input :group_order_unit, as: :select, collection: [], input_html: {'data-initial-value': changed_article.group_order_unit, class: 'input-medium', style: highlight_new(attrs, :group_order_unit)}, label: '×'.html_safe, include_blank: false %td{:style => highlight_new(attrs, :price)} - .input-prepend - %span.add-on= t 'number.currency.format.unit' - = form.text_field 'price', class: 'input-mini', style: 'width: 45px' + .d-flex.gap-1 + .input-prepend + %span.add-on= t 'number.currency.format.unit' + = form.text_field 'price', class: 'input-mini', style: 'width: 45px' + .input.d-flex.gap-1 + %label=t('articles.form.per') + = form.select :price_unit, [], {include_blank: false}, {'data-initial-value': changed_article.price_unit, class: 'input-medium'} %td{:style => highlight_new(attrs, :tax)} .input-append = form.text_field 'tax', class: 'input-mini', style: 'width: 45px' diff --git a/app/views/articles/create.js.haml b/app/views/articles/create.js.haml index 3d8be65f..7fa89fe9 100644 --- a/app/views/articles/create.js.haml +++ b/app/views/articles/create.js.haml @@ -1,3 +1,6 @@ $('#modalContainer').modal('hide'); $('#listbody').prepend('#{escape_javascript(render(@article))}'); +const importActionsCell$ = $('#import #search_results td[data-order-number="#{escape_javascript(@article.order_number)}"]'); +importActionsCell$.find('.icon-ok').show(); +importActionsCell$.find('.article_import_btn').hide(); = render 'import_article_added', article: @article if @article.shared_article diff --git a/app/views/articles/destroy.js.haml b/app/views/articles/destroy.js.haml index da7c5a62..1bb7588f 100644 --- a/app/views/articles/destroy.js.haml +++ b/app/views/articles/destroy.js.haml @@ -2,3 +2,6 @@ $('#article_#{@article.id}').after('#{escape_javascript(render("destroy_active_article"))}'); - else $('#article_#{@article.id}').remove(); + const importActionsCell$ = $('#import #search_results td[data-order-number="#{escape_javascript(@article.order_number)}"]'); + importActionsCell$.find('.icon-ok').hide(); + importActionsCell$.find('.article_import_btn').show(); diff --git a/app/views/articles/edit.html.haml b/app/views/articles/edit.html.haml new file mode 100644 index 00000000..fec1326e --- /dev/null +++ b/app/views/articles/edit.html.haml @@ -0,0 +1,2 @@ + +=render("form") \ No newline at end of file diff --git a/app/views/articles/edit_all.html.haml b/app/views/articles/edit_all.html.haml index ec865295..35db7515 100644 --- a/app/views/articles/edit_all.html.haml +++ b/app/views/articles/edit_all.html.haml @@ -3,8 +3,9 @@ %i= t '.note' = form_tag(update_all_supplier_articles_path(@supplier)) do = render 'edit_all_table' + = hidden_field_tag :complete_migration, true unless @original_units.nil? %br/ %i= t '.warning' .form-actions - = submit_tag t('.submit'), class: 'btn btn-primary' + = submit_tag t('.submit'), class: 'btn btn-primary', disabled: true = link_to t('ui.or_cancel'), supplier_articles_path(@supplier) diff --git a/app/views/articles/index.html.haml b/app/views/articles/index.html.haml index fbb549fb..ff1dc903 100644 --- a/app/views/articles/index.html.haml +++ b/app/views/articles/index.html.haml @@ -16,29 +16,31 @@ = text_field_tag :query, params[:query], class: 'input-medium search-query', placeholder: t('.search_placeholder') - - if @supplier.shared_supplier + - unless @supplier.supplier_remote_source.blank? .btn-group - if @supplier.shared_sync_method == 'import' = link_to t('.ext_db.import'), "#import", 'data-toggle-this' => '#import', class: 'btn btn-primary' = link_to t('.ext_db.sync'), sync_supplier_articles_path(@supplier), method: :post, class: 'btn btn-success' .btn-group - = link_to t('.new'), new_supplier_article_path(@supplier), remote: true, class: "btn #{'btn-primary' unless @supplier.shared_supplier}" + = link_to t('.migrate_units') + '...', prepare_units_migration_supplier_articles_path(@supplier), class: 'btn btn-success' if @supplier.unit_migration_completed.nil? + .btn-group + = link_to t('.new'), new_supplier_article_path(@supplier), remote: true, class: "btn #{'btn-primary' if @supplier.supplier_remote_source.nil?}" = link_to t('.edit_all'), edit_all_supplier_articles_path(@supplier), class: 'btn' = link_to t('.upload'), upload_supplier_articles_path(@supplier), class: 'btn' = link_to t('.download'), supplier_articles_path(@supplier, format: :csv), class: 'btn' - if current_user.role_orders? = link_to t('.new_order'), new_order_path(supplier_id: @supplier), class: 'btn' -- unless @supplier.shared_supplier.nil? +- unless @supplier.supplier_remote_source.blank? #import.well.well-small(style="display:none;") - = form_tag shared_supplier_articles_path(@supplier), method: :get, remote: true, class: 'form-search', + = form_tag supplier_remote_articles_path(@supplier), method: :get, remote: true, class: 'form-search', 'data-submit-onchange' => true do %h3{style: 'display: inline; vertical-align: middle; margin-right: 1em;'}= t('.import.title') + ' ' - = text_field_tag "name_cont_all_joined", "", class: 'input-medium search-query', + = text_field_tag "name", "", class: 'input-medium search-query', placeholder: t('.import.placeholder') %label.checkbox - = check_box_tag "q[origin_eq]", "REG", false + = check_box_tag "origin", "REG", false = t '.import.restrict_region' .pull-right{style: 'line-height: 40px'} = label_tag :article_category_id, t('.import.category') @@ -64,11 +66,3 @@ $('#import #article_category_id').val(''); } }); - // propagate category select to article import - // XXX quite a url hack - $(document).on('touchclick', '#import .actions a[href]', function() { - var category_id = $('#import #article_category_id').children(':selected').val(); - var url = $(this).attr('href'); - url = url.replace(/(&article_category_id=[0-9]*)?$/, '&article_category_id='+category_id); - $(this).attr('href', url); - }); diff --git a/app/views/articles/migrate_units.html.haml b/app/views/articles/migrate_units.html.haml new file mode 100644 index 00000000..f146e765 --- /dev/null +++ b/app/views/articles/migrate_units.html.haml @@ -0,0 +1,74 @@ +- title t('.title', supplier: @supplier.name) +%span= simple_format(t('.explanation')) +- content_for :javascript do + :javascript + const units = #{raw(@all_units.to_json)}; +- content_for :javascript do + = simple_fields_for "samples[TEMPLATEREPLACEMARKER]" do |form| + :javascript + const ratioTemplateHtml = "#{escape_javascript(render(partial: 'shared/article_unit_ratio', locals: {article_unit_ratio: @empty_article_unit_ratio, f: form, article_unit_ratio_counter: -1}))}"; + :javascript + $(document).ready(() => new MigrateUnitsForm(ratioTemplateHtml, $('#migrate-units'), units)); +- price_markup = FoodsoftConfig[:price_markup].to_f += render partial: 'shared/js_templates/unit_conversion_popover_template' + += form_with url: complete_units_migration_supplier_articles_path do |f| + %table.table#migrate-units + %thead + %tr + %th= t('.column_headers.apply') + %th= t('.column_headers.original_plaintext_unit') + %th= t('.column_headers.affected_articles') + %th= heading_helper ArticleVersion, :supplier_order_unit + %th= t('.column_headers.contains') + %th= heading_helper ArticleVersion, :group_order_granularity + %tbody + - @samples.each_with_index do |sample, index| + %tr{:id => "sample_row_#{index}"} + = simple_fields_for "samples[#{index}]", sample do |form| + %td + = yield form # allow to add hidden fields to form + = form.input :apply_migration, as: :boolean, label: false, input_html: { value: true, checked: 'checked' } + %td + - sample[:articles].each do |article| + = hidden_field_tag "samples[#{index}][article_ids][]", article.id + = "\"#{sample[:unit]}\"" + %td + %span.articles-list + %span.expander{role: :button} + %i.icon-chevron-sign-right + %span.collapser.d-none{role: :button} + %i.icon-chevron-sign-down + %span + = sample[:articles].length + %ul.list.d-none + - sample[:articles].each do |article| + %li + - if article.availability + = article.name + - else + %s=article.name + + %td + = form.input :supplier_order_unit, as: :select, collection: [], label: false, include_blank: t('.custom_unit'), input_html: {class: 'input-medium', 'data-initial-value': sample[:conversion_result][:supplier_order_unit]} + %td + .d-flex.gap-1 + = field_with_preset_value_and_errors(form: form, + field: :first_ratio_quantity, + value: sample[:conversion_result][:first_ratio]&.dig(:quantity), + errors: sample[:errors]&.messages&.dig(:"latest_article_version.article_unit_ratios.quantity"), + input_html: { type: :number, class: 'input-mini', required: true }) + %span + = '×'.html_safe + = form.input :first_ratio_unit, as: :select, include_blank: true, collection: [], required: false, label: false, input_html: {class: 'input-medium', 'data-initial-value': sample[:conversion_result][:first_ratio]&.dig(:unit)} + %td + .d-flex.gap-1 + = field_with_preset_value_and_errors(form: form, + field: :group_order_granularity, + value: sample[:conversion_result][:group_order_granularity], + errors: sample[:errors]&.messages&.dig(:"latest_article_version.group_order_granularity"), + input_html: { type: :number, class: 'input-mini', required: true, title: "steps in which ordergroups can order this article" }) + %span + = '×'.html_safe + = form.input :group_order_unit, as: :select, collection: [], input_html: {'data-initial-value': sample[:conversion_result][:group_order_unit], class: 'input-medium'}, label: false, include_blank: false + = f.submit t('.run_migration'), class: 'btn btn-primary' diff --git a/app/views/articles/new.js.haml b/app/views/articles/new.js.haml index 504f5527..1424b96f 100644 --- a/app/views/articles/new.js.haml +++ b/app/views/articles/new.js.haml @@ -1,2 +1,2 @@ -$('#modalContainer').html('#{j(render("form"))}'); +$('#modalContainer').addClass('modal-xl').html('#{j(render("form"))}'); $('#modalContainer').modal(); diff --git a/app/views/articles/prepare_units_migration.haml b/app/views/articles/prepare_units_migration.haml new file mode 100644 index 00000000..5e22ee7b --- /dev/null +++ b/app/views/articles/prepare_units_migration.haml @@ -0,0 +1,8 @@ +- title t('.title', supplier: @supplier.name) +%p.mb-1 + =simple_format(t('.explanation')) += link_to t('.download'), supplier_articles_path(@supplier, format: :csv), class: 'btn' +%br/ +%br/ +%p.mt-1 + = link_to t('.migrate_units') + '...', migrate_units_supplier_articles_path(@supplier), class: 'btn btn-success' if @supplier.unit_migration_completed.nil? diff --git a/app/views/articles/shared.js.haml b/app/views/articles/shared.js.haml deleted file mode 100644 index 23816d7e..00000000 --- a/app/views/articles/shared.js.haml +++ /dev/null @@ -1 +0,0 @@ -$('#search_results').html('#{escape_javascript(render("import_search_results"))}'); diff --git a/app/views/articles/update.js.haml b/app/views/articles/update.js.haml index e007307e..71a2ed0b 100644 --- a/app/views/articles/update.js.haml +++ b/app/views/articles/update.js.haml @@ -1,2 +1,2 @@ $('#article_#{@article.id}').replaceWith('#{escape_javascript(render(@article))}'); -$('#modalContainer').modal('hide'); +$('#modalContainer').removeClass('modal-xl').modal('hide'); diff --git a/app/views/deliveries/_stock_article_for_adding.html.haml b/app/views/deliveries/_stock_article_for_adding.html.haml index 5463bc8f..83356365 100644 --- a/app/views/deliveries/_stock_article_for_adding.html.haml +++ b/app/views/deliveries/_stock_article_for_adding.html.haml @@ -4,8 +4,8 @@ %tr{:id => "stock_article_#{article.id}", :class => css_class} %td= article.name - %td{:data => {:toggle => :tooltip, :title => "#{render(:partial => 'shared/article_price_info', :locals => {:article => article})}"}}= number_to_currency article.price - %td= article.unit + %td{:data => {:toggle => :tooltip, :title => "#{render(:partial => 'shared/article_version_info', :locals => {:article => article})}"}}= number_to_currency article.price + %td= format_supplier_order_unit(article) %td= article.article_category.name %td = link_to t('.action_edit'), edit_stock_article_path(article), remote: true, class: 'btn btn-mini' diff --git a/app/views/deliveries/_stock_change_fields.html.haml b/app/views/deliveries/_stock_change_fields.html.haml index 40ec5922..65418acc 100644 --- a/app/views/deliveries/_stock_change_fields.html.haml +++ b/app/views/deliveries/_stock_change_fields.html.haml @@ -4,7 +4,7 @@ %td %span.stock_article_name= stock_change.stock_article.name = f.association :stock_article, :as => :hidden - %td.price{:data => {:toggle => :tooltip, :title => "#{render(:partial => 'shared/article_price_info', :locals => {:article => stock_article})}"}}= number_to_currency stock_article.price - %td.unit= stock_change.stock_article.unit + %td.price{:data => {:toggle => :tooltip, :title => "#{render(:partial => 'shared/article_version_info', :locals => {:article => stock_article})}"}}= number_to_currency stock_article.price + %td.unit= format_supplier_order_unit(stock_change.stock_article) %td= f.input :quantity, :wrapper => :intable, :input_html => {:class => 'stock-change-quantity', :autocomplete => :off} %td= stock_change_remove_link f diff --git a/app/views/deliveries/add_stock_change.js.erb b/app/views/deliveries/add_stock_change.js.erb index 2d01bc68..611f7c4d 100644 --- a/app/views/deliveries/add_stock_change.js.erb +++ b/app/views/deliveries/add_stock_change.js.erb @@ -2,21 +2,21 @@ if(!is_article_available_for_delivery(<%= @stock_change.stock_article.id %>)) { return false; } - + $('#stock_changes tr').removeClass('success'); - - var quantity = w.prompt('<%= j(t('.how_many_units', :unit => @stock_change.stock_article.unit, :name => @stock_change.stock_article.name)) %>'); + + var quantity = w.prompt('<%= j(t('.how_many_units', :unit => format_supplier_order_unit(@stock_change.stock_article), :name => @stock_change.stock_article.name)) %>'); if(null === quantity) { return false; } - + var stock_change = $( '<%= j(render(:partial => 'stock_change', :locals => {:stock_change => @stock_change})) %>' ).addClass('success'); $('input.stock-change-quantity', stock_change).val(quantity); - + $('#stock_changes').append(stock_change); mark_article_for_delivery(<%= @stock_change.stock_article.id %>); updateSort('#stock_changes'); - + })(window); diff --git a/app/views/finance/balancing/_group_order_articles.html.haml b/app/views/finance/balancing/_group_order_articles.html.haml index 896cea3c..41f21640 100644 --- a/app/views/finance/balancing/_group_order_articles.html.haml +++ b/app/views/finance/balancing/_group_order_articles.html.haml @@ -20,13 +20,14 @@ %td{:style=>"width:50%"} = group_order_article.group_order.ordergroup_name %td.center= group_order_article_edit_result(group_order_article) - %td.numeric= number_to_currency(group_order_article.order_article.price.fc_price * group_order_article.result) + %td.numeric= number_to_currency(group_order_article.order_article.article_version.fc_group_order_price * group_order_article.result) %td.actions{:style=>"width:1em"} - unless order_article.order.closed? = link_to t('ui.delete'), group_order_article_path(group_order_article), method: :delete, remote: true, class: 'btn btn-mini btn-danger' %td - - totals[:result] += group_order_article.result + - article_version = order_article.article_version + - totals[:result] += article_version.convert_quantity(group_order_article.result, article_version.group_order_unit, article_version.billing_unit) %tfoot %tr %td diff --git a/app/views/finance/balancing/_order_article.html.haml b/app/views/finance/balancing/_order_article.html.haml index 47db3e31..f7942a7a 100644 --- a/app/views/finance/balancing/_order_article.html.haml +++ b/app/views/finance/balancing/_order_article.html.haml @@ -1,24 +1,24 @@ %td.closed.name - = link_to order_article.article.name, '#', 'data-toggle-this' => "#group_order_articles_#{order_article.id}" -%td= order_article.article.order_number -%td{title: units_history_line(order_article, :plain => true)} - = order_article.units - = pkg_helper order_article.article_price - - if s=order_article.ordered_quantities_different_from_group_orders? + = link_to order_article.article_version.name, '#', 'data-toggle-this' => "#group_order_articles_#{order_article.id}" +%td= order_article.article_version.order_number +%td{title: units_history_line(order_article, plain: true, unit: order_article.article_version.billing_unit)} + = order_article.article_version.convert_quantity(order_article.units, order_article.article_version.supplier_order_unit, order_article.article_version.billing_unit).round(3).to_s + = pkg_helper(order_article.article_version, unit: order_article.article_version.billing_unit) + - if s=ordered_quantities_different_from_group_orders?(order_article) %span{:style => "color:red;font-weight: bold"}= s -%td #{order_article.article.unit} +%td #{format_billing_unit_with_ratios(order_article.article_version)} %td - = number_to_currency(order_article.price.price, :unit => "") + = number_to_currency(order_article.article_version.price, :unit => "") :plain / = number_to_currency(order_article.total_price, :unit => "") %td - = number_to_currency(order_article.price.gross_price, :unit => "") + = number_to_currency(order_article.article_version.gross_price, :unit => "") :plain / = number_to_currency(order_article.total_gross_price, :unit => "") -%td= number_to_percentage(order_article.price.tax) unless order_article.price.tax.zero? -%td= number_to_currency(order_article.price.deposit, :unit => "") unless order_article.price.deposit.zero? +%td= number_to_percentage(order_article.article_version.tax) unless order_article.article_version.tax.zero? +%td= number_to_currency(order_article.article_version.deposit, :unit => "") unless order_article.article_version.deposit.zero? %td = link_to t('ui.edit'), edit_order_order_article_path(order_article.order, order_article), remote: true, class: 'btn btn-mini' unless order_article.order.closed? diff --git a/app/views/finance/balancing/new.html.haml b/app/views/finance/balancing/new.html.haml index 7e143e08..5349ddb1 100644 --- a/app/views/finance/balancing/new.html.haml +++ b/app/views/finance/balancing/new.html.haml @@ -1,3 +1,4 @@ += render partial: 'shared/js_templates/unit_conversion_popover_template', locals: {set_units_data: true} - content_for :javascript do :javascript $(function() { @@ -11,7 +12,7 @@ contentType: 'application/json; charset=UTF-8' }); }); - + $(document).on('OrderArticle#create', function(e) { $.ajax({ url: '#{new_on_order_article_create_finance_order_path(@order)}', @@ -20,6 +21,12 @@ contentType: 'application/json; charset=UTF-8' }); }); + + $('*[name="group_order_article[result]"]').each((_, field) => $(field).unitConversionField({ + units: unitsData, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + useTargetUnitForStep: false + })); }); = render 'shared/articles_by/common', order: @order @@ -40,7 +47,7 @@ - unless @order.note.blank? = simple_format @order.note - else - %p= t('.comment_on_transaction') + %p= t('.comment_on_transaction') = link_to t('.edit_note'), edit_note_finance_order_path(@order), remote: true .well.well-small diff --git a/app/views/finance/balancing/new.js.haml b/app/views/finance/balancing/new.js.haml index 0bd073fd..224e225f 100644 --- a/app/views/finance/balancing/new.js.haml +++ b/app/views/finance/balancing/new.js.haml @@ -1 +1,6 @@ $('#results').html('#{j(render(balancing_view_partial, order: @order))}'); +$('*[name="group_order_article[result]"]').each((_, field) => $(field).unitConversionField({ + units: unitsData, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + useTargetUnitForStep: false +})); diff --git a/app/views/finance/balancing/new_on_order_article_create.js.erb b/app/views/finance/balancing/new_on_order_article_create.js.erb index 5e55a80a..9a88243c 100644 --- a/app/views/finance/balancing/new_on_order_article_create.js.erb +++ b/app/views/finance/balancing/new_on_order_article_create.js.erb @@ -2,15 +2,21 @@ // See publish/subscribe design pattern in /doc. (function(w) { $('#order_article_<%= @order_article.id %>').remove(); // just to be sure: remove table row which is added below - + $('#ordered-articles tr').removeClass('success'); - + var order_article_entry = $( '<%= j render('finance/balancing/order_article_result', order_article: @order_article) %>' ).addClass('success'); - + $('#result_table').prepend(order_article_entry); - + $('#summaryChangedWarning').show(); + + $('*[name="group_order_article[result]"]').each((_, field) => $(field).unitConversionField({ + units: unitsData, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + useTargetUnitForStep: false + })); })(window); diff --git a/app/views/finance/balancing/new_on_order_article_update.js.erb b/app/views/finance/balancing/new_on_order_article_update.js.erb index a3fe6c27..71a4c314 100644 --- a/app/views/finance/balancing/new_on_order_article_update.js.erb +++ b/app/views/finance/balancing/new_on_order_article_update.js.erb @@ -4,11 +4,17 @@ $('#order_article_<%= @order_article.id %>').html( '<%= j render('finance/balancing/order_article', order_article: @order_article) %>' ); - + $('#group_order_articles_<%= @order_article.id %>').html( '<%= j render('finance/balancing/group_order_articles', order_article: @order_article) %>' ); - + $('#summaryChangedWarning').show(); + + $('*[name="group_order_article[result]"]').each((_, field) => $(field).unitConversionField({ + units: unitsData, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + useTargetUnitForStep: false + })); })(window); diff --git a/app/views/group_order_articles/_form.html.haml b/app/views/group_order_articles/_form.html.haml index e0921d4e..7a7ade62 100644 --- a/app/views/group_order_articles/_form.html.haml +++ b/app/views/group_order_articles/_form.html.haml @@ -2,10 +2,10 @@ = form.hidden_field :order_article_id .modal-header = close_button :modal - %h3= t('.amount_change_for', article: @order_article.article.name) + %h3= t('.amount_change_for', article: @order_article.article_version.name) .modal-body = form.input :ordergroup_id, as: :select, collection: Ordergroup.undeleted.map { |g| [g.name, g.id] } - = form.input :result, hint: I18n.t('group_order_articles.form.result_hint', unit: @order_article.article.unit) # Why do we need the full prefix? + = form.input :result, hint: I18n.t('group_order_articles.form.result_hint', unit: format_group_order_unit_with_ratios(@order_article.article_version)) # Why do we need the full prefix? .modal-footer = link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'} = form.submit t('ui.save'), class: 'btn btn-primary' diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 3ffd583e..d937a8f6 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -1,12 +1,15 @@ += render partial: 'shared/js_templates/unit_conversion_popover_template', locals: {set_units_data: true} - content_for :javascript do - group_balance = FoodsoftConfig[:charge_members_manually] ? @ordering_data[:account_balance] : @ordering_data[:available_funds] :javascript + new GroupOrderForm($('.group_order_form'), { + units: unitsData, + toleranceIsCostly: #{FoodsoftConfig[:tolerance_is_costly]}, + groupBalance: #{group_balance}, + minimumBalance: #{FoodsoftConfig[:minimum_balance] or 0}, + stockit: #{@order.stockit?} + }); $(function() { - #{data_to_js(@ordering_data)} - setGroupBalance(#{group_balance}); - setMinimumBalance(#{FoodsoftConfig[:minimum_balance] or 0}); - setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]}); - setStockit(#{@order.stockit?}); // create List for search-feature (using list.js, http://listjs.com) var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}]; var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}]; @@ -21,7 +24,6 @@ }); - title t('.title'), false - .row-fluid .well.pull-left = close_button :alert @@ -64,7 +66,7 @@ %button.add-on.btn.reset-search{:type => :button, :title => t('.reset_article_search')} %i.icon.icon-remove -= form_for @group_order do |f| += form_for @group_order, html: {class: 'group_order_form'} do |f| = f.hidden_field :lock_version = f.hidden_field :order_id = f.hidden_field :updated_by_user_id @@ -94,46 +96,55 @@ %i.icon-tag %td{colspan: "9"} - order_articles.each do |order_article| - %tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top"} - %td.name= order_article.article.name + %tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units], order_article.article_version)}", valign: "top"} + %td.name= order_article.article_version.name - if @order.stockit? - %td= truncate order_article.article.supplier.name, length: 15 - %td= h order_article.article.origin + %td= truncate order_article.article_version.article.supplier.name, length: 15 + %td= h order_article.article_version.origin %td= number_to_currency(@ordering_data[:order_articles][order_article.id][:price]) - %td= order_article.article.unit + %td.group-order-unit= format_group_order_unit_with_ratios(order_article.article_version) %td - if @order.stockit? = @ordering_data[:order_articles][order_article.id][:quantity_available] - else - %span{id: "missing_units_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:missing_units] + %span.missing-units= @ordering_data[:order_articles][order_article.id][:missing_units] - %td.quantity - %input{id: "q_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][quantity]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:quantity], 'data-min' => (@ordering_data[:order_articles][order_article.id][:quantity] if @order.boxfill?), 'data-max' => (@ordering_data[:order_articles][order_article.id][:quantity]+@ordering_data[:order_articles][order_article.id][:missing_units] if @order.boxfill?)}/ - %span.used{id: "q_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_quantity] + - quantity_data = ratio_quantity_data(order_article, order_article.article_version.group_order_unit) + - quantity_data['ratio-group-order-unit-supplier-unit'] = @ordering_data[:order_articles][order_article.id][:ratio_group_order_unit_supplier_unit] + - quantity_data['others_quantity'] = @ordering_data[:order_articles][order_article.id][:others_quantity] + - quantity_data['others_tolerance'] = @ordering_data[:order_articles][order_article.id][:others_tolerance] + - quantity_data['used_quantity'] = @ordering_data[:order_articles][order_article.id][:used_quantity] + - quantity_data['price'] = @ordering_data[:order_articles][order_article.id][:price] + - quantity_data['minimum_order_quantity'] = @ordering_data[:order_articles][order_article.id][:minimum_order_quantity] unless @ordering_data[:order_articles][order_article.id][:minimum_order_quantity].nil? + %td.quantity.group-order-input + %span.used= format_number(@ordering_data[:order_articles][order_article.id][:used_quantity]) + - %span.unused{id: "q_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:quantity] - @ordering_data[:order_articles][order_article.id][:used_quantity] - .btn-group - %a.btn.btn-ordering{'data-increase_quantity' => order_article.id} + %span.unused= format_number(@ordering_data[:order_articles][order_article.id][:quantity] - @ordering_data[:order_articles][order_article.id][:used_quantity]) + .btn-group.numeric-step + %a.btn.btn-ordering.increase %i.icon-plus - %a.btn.btn-ordering{'data-decrease_quantity' => order_article.id} + %input.goa-quantity{type: "number", name: "group_order[group_order_articles_attributes][#{order_article.id}][quantity]", value: @ordering_data[:order_articles][order_article.id][:quantity], data: quantity_data, autocomplete: 'off', class: 'input-mini numeric', min: 0, step: order_article.article_version.group_order_granularity} + %a.btn.btn-ordering.decrease %i.icon-minus + %span.numeric-step-error + = t('errors.step_error', granularity: order_article.article_version.group_order_granularity, min: 0) - %td.tolerance{style: ('display:none' if @order.stockit?)} - %input{id: "t_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][tolerance]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:tolerance], 'data-min' => (@ordering_data[:order_articles][order_article.id][:tolerance] if @order.boxfill?)}/ - - if (@ordering_data[:order_articles][order_article.id][:unit] > 1) - %span.used{id: "t_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_tolerance] + %td.tolerance.group-order-input{style: ('display:none' if @order.stockit?)} + - if (requires_tolerance_input?(order_article, @ordering_data)) + %span.used= format_number(@ordering_data[:order_articles][order_article.id][:used_tolerance]) + - %span.unused{id: "t_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] - @ordering_data[:order_articles][order_article.id][:used_tolerance] + %span.unused= format_number(@ordering_data[:order_articles][order_article.id][:tolerance] - @ordering_data[:order_articles][order_article.id][:used_tolerance]) .btn-group - %a.btn.btn-ordering{'data-increase_tolerance' => order_article.id} + %a.btn.btn-ordering.increase %i.icon-plus - %a.btn.btn-ordering{'data-decrease_tolerance' => order_article.id} + %input.goa-tolerance{type: "number", name: "group_order[group_order_articles_attributes][#{order_article.id}][tolerance]", data: quantity_data, value: @ordering_data[:order_articles][order_article.id][:tolerance], autocomplete: 'off', class: 'input-mini numeric', min: 0, step: order_article.article_version.group_order_granularity, max: [@ordering_data[:order_articles][order_article.id][:ratio_group_order_unit_supplier_unit], @ordering_data[:order_articles][order_article.id][:minimum_order_quantity].presence || 0].max} + %a.btn.btn-ordering.decrease %i.icon-minus %td{id: "td_price_#{order_article.id}", style: "text-align:right; padding-right:10px; width:4em"} - %span{id: "price_#{order_article.id}_display"}= number_to_currency(@ordering_data[:order_articles][order_article.id][:total_price]) + %span{id: "price_#{order_article.id}_display", data: {price: @ordering_data[:order_articles][order_article.id][:total_price]}}= number_to_currency(@ordering_data[:order_articles][order_article.id][:total_price]) .article-info - .article-name= order_article.article.name + .article-name= order_article.article_version.name .pull-right = t('.units_full') + ':' %span{id: "units_#{order_article.id}"}= order_article.units_to_order @@ -145,11 +156,11 @@ %span{id: "t_total_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] + @ordering_data[:order_articles][order_article.id][:others_tolerance] %br/ .pull-left - #{heading_helper Article, :manufacturer}: #{order_article.article.manufacturer} + #{heading_helper Article, :manufacturer}: #{order_article.article_version.manufacturer} %br/ - #{heading_helper Article, :units}: #{@order.stockit? ? order_article.article.quantity_available : @ordering_data[:order_articles][order_article.id][:unit]} * #{h order_article.article.unit} + #{heading_helper Article, :units}: #{@order.stockit? ? order_article.article_version.article.quantity_available : format_supplier_order_unit_with_ratios(order_article.article_version)} %br/ - #{heading_helper Article, :note}: #{order_article.article.note} + #{heading_helper Article, :note}: #{order_article.article_version.note} %br/ #order-footer #info-box diff --git a/app/views/group_orders/show.html.haml b/app/views/group_orders/show.html.haml index 8c9678d7..47f0ca6a 100644 --- a/app/views/group_orders/show.html.haml +++ b/app/views/group_orders/show.html.haml @@ -72,19 +72,19 @@ - r = get_order_results(oa, @group_order.id) %tr{class: cycle('even', 'odd', name: 'articles') + " " + order_article_class_name(r[:quantity], r[:tolerance], r[:result])} %td{style: "width:40%"} - = oa.article.name - - unless oa.article.note.blank? + = oa.article_version.name + - unless oa.article_version. note.blank? = image_tag("lamp_grey.png", {alt: t('.articles.show_note'), size: "15x16", border: "0", onmouseover: "$('#note_#{oa.id}').show();", onmouseout: "$('#note_#{oa.id}').hide();"}) - %td= "#{oa.price.unit_quantity} x #{oa.article.unit}" - %td= number_to_currency(oa.price.fc_price) + %td= format_group_order_unit_with_ratios(oa.article_version) + %td= number_to_currency(oa.article_version.fc_group_order_price) %td = r[:quantity] - = "+ #{r[:tolerance]}" if oa.price.unit_quantity > 1 + = "+ #{r[:tolerance]}" if oa.article_version.unit_quantity > 1 %td= r[:result] > 0 ? r[:result] : "0" %td= number_to_currency(r[:sub_total]) - - unless oa.article.note.blank? + - unless oa.article_version. note.blank? %tr{id: "note_#{oa.id}", class: "note even", style: "display:none"} - %td{colspan: "6"}=h oa.article.note + %td{colspan: "6"}=h oa.article_version. note %tr{class: cycle('even', 'odd', name: 'articles')} %th{colspan: "5"}= heading_helper GroupOrder, :price %th= number_to_currency(@group_order.price) diff --git a/app/views/mailer/order_received.text.haml b/app/views/mailer/order_received.text.haml index 79f01c5d..8ff80c25 100644 --- a/app/views/mailer/order_received.text.haml +++ b/app/views/mailer/order_received.text.haml @@ -3,11 +3,11 @@ - unless @abundant_articles.empty? = raw "===\n" + t('.abundant_articles') + ":" - @abundant_articles.each do |order_article| - - article = order_article.article + - article = order_article.article_version = raw t('.article_details', name: article.name, ordered: order_article.units_to_order, received: order_article.units_received, unit: article.unit) - unless @scarce_articles.empty? = raw "===\n" + t('.scarce_articles') + ":" - @scarce_articles.each do |order_article| - - article = order_article.article + - article = order_article.article_version = raw t('.article_details', name: article.name, ordered: order_article.units_to_order, received: order_article.units_received, unit: article.unit) diff --git a/app/views/mailer/order_result.text.haml b/app/views/mailer/order_result.text.haml index 077207d7..3900fa86 100644 --- a/app/views/mailer/order_result.text.haml +++ b/app/views/mailer/order_result.text.haml @@ -3,6 +3,6 @@ = raw t '.text1', pickup: I18n.l(@order.pickup) = raw t '.text2' - for group_order_article in @group_order.group_order_articles.ordered.includes(:order_article) - - article = group_order_article.order_article.article + - article = group_order_article.order_article.article_version \- #{article.name}: #{group_order_article.result} x #{article.unit} = #{group_order_article.result * article.fc_price} = raw t '.text3', sum: @group_order.price, order_url: group_order_url(@group_order), foodcoop: FoodsoftConfig[:name] diff --git a/app/views/order_articles/_edit.html.haml b/app/views/order_articles/_edit.html.haml index 047e1391..41e09e7f 100644 --- a/app/views/order_articles/_edit.html.haml +++ b/app/views/order_articles/_edit.html.haml @@ -2,28 +2,33 @@ .modal-header = close_button :modal %h3= t '.title' - .modal-body + .modal-body.article-form - if params[:without_units] = hidden_field_tag :without_units, true - else .fold-line - = form.input :units_to_order, hint: '', input_html: {class: 'input-nano'} + = form.input :units_to_order, hint: '', input_html: {class: 'input-mini', step: 0.001} -#= form.input :units_billed, label: 'invoice', input_html: {class: 'input-nano'} - = form.input :units_received, input_html: {class: 'input-nano'}, + = form.input :units_received, input_html: {class: 'input-mini', step: 0.001}, label: t('activerecord.attributes.order_article.units_received_short') %p.help-block= t 'simple_form.hints.order_article.units_to_order' .foo{style: 'clear:both'} - = simple_fields_for :article, @order_article.article do |f| + = simple_fields_for @order_article.article_version do |f| = f.input :name - - if @order_article.article.is_a?(StockArticle) + - if @order_article.article_version.is_a?(StockArticle) %div.alert= t '.stock_alert' - else - = simple_fields_for :article_price, @order_article.article_price do |fprice| - = render partial: 'shared/article_fields_units', locals: {f_unit: f, f_uq: fprice} - = render partial: 'shared/article_fields_price', locals: {f: fprice} + = simple_fields_for :article_version, @order_article.article_version do |fprice| + :javascript + const articleUnitRatioTemplate$ = $($.parseHTML("#{escape_javascript(render(partial: 'shared/article_unit_ratio', locals: {article_unit_ratio: @empty_article_unit_ratio, f: fprice, article_unit_ratio_counter: -1}))}")); + const units = #{raw(@all_units.to_json)}; + new ArticleForm(articleUnitRatioTemplate$, $('.article-form').parents('form'), units, #{FoodsoftConfig[:price_markup].to_f}); + = render partial: 'shared/js_templates/unit_conversion_popover_template' + = render partial: 'shared/article_fields_units', locals: {f: fprice, article: @order_article.article_version} + = render partial: 'shared/article_fields_price', locals: {f: fprice, article: @order_article.article_version} = form.input :update_global_price, as: :boolean = f.input :order_number diff --git a/app/views/order_articles/edit.js.haml b/app/views/order_articles/edit.js.haml index 979e34cb..e06d68ae 100644 --- a/app/views/order_articles/edit.js.haml +++ b/app/views/order_articles/edit.js.haml @@ -1,2 +1,2 @@ -$('#modalContainer').html('#{j(render("edit"))}'); -$('#modalContainer').modal(); \ No newline at end of file +$('#modalContainer').addClass('modal-xl').html('#{j(render("edit"))}'); +$('#modalContainer').modal(); diff --git a/app/views/order_articles/new.js.haml b/app/views/order_articles/new.js.haml index 4a44300f..3168a44d 100644 --- a/app/views/order_articles/new.js.haml +++ b/app/views/order_articles/new.js.haml @@ -1,2 +1,2 @@ $('#modalContainer').html('#{j(render("new"))}'); -$('#modalContainer').modal(); \ No newline at end of file +$('#modalContainer').modal(); diff --git a/app/views/orders/_articles.html.haml b/app/views/orders/_articles.html.haml index 1c800cc7..97270f3a 100644 --- a/app/views/orders/_articles.html.haml +++ b/app/views/orders/_articles.html.haml @@ -18,26 +18,26 @@ %i.icon-tag %td{:colspan => "9"} - order_articles.each do |order_article| - - net_price = order_article.price.price - - gross_price = order_article.price.gross_price - - unit_quantity = order_article.price.unit_quantity + - article = order_article.price + - net_price = article.price + - gross_price = article.gross_price - units = order_article.units - - total_net += units * unit_quantity * net_price - - total_gross += units * unit_quantity * gross_price + - total_net += units * article.price + - total_gross += units * article.gross_price %tr{:class => cycle('even', 'odd', :name => 'articles') + ' ' + order_article_class(order_article)} - %td.name=h order_article.article.name - %td= order_article.article.unit + %td.name=h order_article.article_version.name + %td= format_group_order_unit_with_ratios(order_article.article_version) %td= "#{number_to_currency(net_price)} / #{number_to_currency(gross_price)}" - if order.stockit? %td= units - else - - if unit_quantity > 1 or order_article.tolerance > 0 + - if order_article.tolerance > 0 %td= "#{order_article.quantity} + #{order_article.tolerance}" - else %td= "#{order_article.quantity}" %td{title: units_history_line(order_article, plain: true)} = units - = pkg_helper order_article.price + = pkg_helper article %p = t '.prices_sum' = "#{number_to_currency(total_net)} / #{number_to_currency(total_gross)}" diff --git a/app/views/orders/_edit_amount.html.haml b/app/views/orders/_edit_amount.html.haml index 22e6c4a4..ad334c02 100644 --- a/app/views/orders/_edit_amount.html.haml +++ b/app/views/orders/_edit_amount.html.haml @@ -4,25 +4,25 @@ - cssclass += " unavailable" if (order_article.units_billed||order_article.units_to_order)==0 and order_article.units_received.nil? %tr{id: "order_article_#{order_article.id}", class: cssclass, valign: "top"} - order_title = [] - - order_title.append Article.human_attribute_name(:manufacturer)+': ' + order_article.article.manufacturer unless order_article.article.manufacturer.to_s.empty? - - order_title.append Article.human_attribute_name(:note)+': ' + order_article.article.note unless order_article.article.note.to_s.empty? - %td= order_article.article.order_number - %td.name{title: order_title.join("\n")}= order_article.article.name - %td.unit= order_article.article.unit - %td.article_price - = number_to_currency order_article.article_price.price - = article_price_change_hint(order_article) + - order_title.append Article.human_attribute_name(:manufacturer)+': ' + order_article.article_version.manufacturer unless order_article.article_version.manufacturer.to_s.empty? + - order_title.append Article.human_attribute_name(:note)+': ' + order_article.article_version.note unless order_article.article_version.note.to_s.empty? + %td= order_article.article_version.order_number + %td.name{title: order_title.join("\n")}= order_article.article_version.name + %td.unit= format_group_order_unit_with_ratios(order_article.article_version) + %td.article_version + = number_to_currency order_article.article_version.group_order_price + = article_version_change_hint(order_article) %td #{order_article.quantity} + #{order_article.tolerance} %td = order_article.units_to_order - = pkg_helper order_article.article + = pkg_helper order_article.price -#%td # TODO implement invoice screen - unless order_article.units_billed.nil? = order_article.units_billed - = pkg_helper order_article.article, soft_uq: true + = pkg_helper order_article.article_version, soft_uq: true %td.units_received_cell = receive_input_field(form) - = pkg_helper order_article.article_price, icon: false, soft_uq: true + = pkg_helper order_article.article_version, icon: false, soft_uq: true, unit: order_article.article_version.billing_unit / TODO add almost invisible text_field for entering single units %td.units_delta %td diff --git a/app/views/orders/_edit_amounts.html.haml b/app/views/orders/_edit_amounts.html.haml index 17666c2d..4d897d2f 100644 --- a/app/views/orders/_edit_amounts.html.haml +++ b/app/views/orders/_edit_amounts.html.haml @@ -1,91 +1,15 @@ += render partial: 'shared/js_templates/unit_conversion_popover_template', locals: {set_units_data: true} - new_articles = (@order.supplier.articles.undeleted rescue @order.articles) -- new_article_data = articles_for_select2(new_articles, @order_articles.map(&:article_id)) {|a| "#{a.name} (#{a.unit_quantity}⨯#{a.unit})"} +- current_article_ids = @order_articles.map { |oa| oa.article_version.article_id } +- new_article_data = articles_for_select2(new_articles, current_article_ids) {|a| "#{a.name} (#{a.unit_quantity}⨯#{a.unit})"} - content_for :javascript do :javascript - - function update_delta(input) { - var units = $(input).val(); - var expected = $(input).data('units-expected'); - var delta = Math.round((units-expected)*100)/100.0; - var html; - - if (units.replace(/\s/g,"")=="") { - // no value - html = ''; - } else if (isNaN(units)) { - html = ''; - } else if (delta == 0) { - // equal value - html = ''; - } else { - if (delta < 0) { - html = '- '+(-delta)+''; - } else /*if (units> expected)*/ { - html = '+ '+(delta)+''; - } - // show package icon only if the receive field has one - if ($(input).hasClass('package')) { - html += '#{j pkg_helper_icon}'; - } - } - - $(input).closest('tr').find('.units_delta').html(html); - - // un-dim row when received is nonzero - $(input).closest('tr').toggleClass('unavailable', expected == 0 && html==''); - } - - $(document).on('change keyup', 'input[data-units-expected]', function() { - update_delta(this); - }); - - $(document).on('touchclick', '#order_articles .unlocker', unlock_receive_input_field); - - $(document).on('click', '#set_all_to_zero', function() { - $('tbody input').each(function(i, input) { - $(input).val(0); - update_delta(input); - }); - }) - - $(function() { - $('input[data-units-expected]').each(function() { - update_delta(this); - }); - - init_add_article('#add_article'); - }); - - function init_add_article(sel) { - $(sel).removeAttr('disabled').select2({ - placeholder: '#{j t('orders.receive.add_article')}', - formatNoMatches: function(term) { return '#{j t('.no_articles_available')}';} - // TODO implement adding a new article, like in deliveries - }).on('change', function(e) { - var $input = $(e.target); - var selectedArticleId = $input.val(); - if(!selectedArticleId) { - return false; - } - - $.ajax({ - url: '#{order_order_articles_path(@order)}', - type: 'post', - data: JSON.stringify({order_article: {article_id: selectedArticleId}}), - contentType: 'application/json; charset=UTF-8' - }); - - $input.val('').trigger('change'); - }); - $(sel).val('').trigger('change'); - } - - function unlock_receive_input_field() { - $('.units_received', $(this).closest('tr')).prop('disabled', false).focus(); - $(this).closest('.input-prepend').prop('title', I18n.t('orders.edit_amount.field_unlocked_title')); - $(this).replaceWith(''); - } - + $('.units_received').each((_, field) => $(field).unitConversionField({ + units: unitsData, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + useTargetUnitForStep: false + })); + $('table#order_articles').parents('form').receiveOrderForm({packageHelperIcon: '#{j pkg_helper_icon}', newOrderArticlePath:'#{order_order_articles_path(@order)}'}); %table#order_articles.ordered-articles.table.table-striped.stupidtable{style: 'margin-bottom: 0'} %thead %tr diff --git a/app/views/orders/_form.html.haml b/app/views/orders/_form.html.haml index ec3db1f3..c73aa9fe 100644 --- a/app/views/orders/_form.html.haml +++ b/app/views/orders/_form.html.haml @@ -50,9 +50,7 @@ %td= truncate article.origin, length: 15, tooltip: true %td= truncate article.manufacturer, length: 15, tooltip: true %td - = article.unit - - if article.unit_quantity > 1 - %span{style: 'color: grey'} × #{article.unit_quantity}#{pkg_helper_icon} + = format_supplier_order_unit_with_ratios(article) %td= "#{number_to_currency(article.price)} / #{number_to_currency(article.fc_price)}" %tr %td diff --git a/app/views/orders/add_article.js.erb b/app/views/orders/add_article.js.erb index 7343d501..e43e3a49 100644 --- a/app/views/orders/add_article.js.erb +++ b/app/views/orders/add_article.js.erb @@ -1,14 +1,14 @@ $('div.container-fluid').prepend( - '<%= j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.notice', :name => @order_article.article.name)})) %>' + '<%= j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.notice', :name => @order_article.article_version.name)})) %>' ); (function() { $('.ordered-articles tr').removeClass('success'); - + var article_for_adding = $( '<%= j(render(:partial => 'edit_amount', :locals => {:order_article => @order_article})) %>' ).addClass('success'); - + $('.ordered-articles tbody').append(article_for_adding); updateSort('.ordered-articles'); diff --git a/app/views/orders/receive_on_order_article_create.js.erb b/app/views/orders/receive_on_order_article_create.js.erb index ed37975f..464ffe1e 100644 --- a/app/views/orders/receive_on_order_article_create.js.erb +++ b/app/views/orders/receive_on_order_article_create.js.erb @@ -2,16 +2,16 @@ // See publish/subscribe design pattern in /doc. (function(w) { $('#order_article_<%= @order_article.id %>').remove(); // just to be sure: remove table row which is added below - + $('#order_articles tr').removeClass('success'); - + var order_article_entry = $( '<%= j render(partial: 'edit_amount', locals: {order_article: @order_article}) %>' ).addClass('success'); - + $('#order_articles tbody').append(order_article_entry); updateSort('#order_articles'); - - $('#add_article_<%= @order_article.article.id %>').remove(); // remove option to add this article + + $('#add_article_<%= @order_article.article_version.id %>').remove(); // remove option to add this article })(window); diff --git a/app/views/orders/receive_on_order_article_update.js.erb b/app/views/orders/receive_on_order_article_update.js.erb index d4ee9a72..1c834707 100644 --- a/app/views/orders/receive_on_order_article_update.js.erb +++ b/app/views/orders/receive_on_order_article_update.js.erb @@ -1,35 +1,9 @@ // Handle more advanced DOM update after AJAX database manipulation. // See publish/subscribe design pattern in /doc. (function(w) { - // get old element and update the cell which is reused - var old_order_article_entry = $('#order_article_<%= @order_article.id %>'); - - // update package info after input - $('td.units_received_cell span.package', old_order_article_entry).remove(); - $('<%= j pkg_helper(@order_article.article_price, icon: false) %>') - .appendTo($('td.units_received_cell', old_order_article_entry)); - - // update package icon on input too - $('input', old_order_article_entry).toggleClass('package', <%= @order_article.article_price.unit_quantity == 1 ? 'false' : 'true' %>); - - // update expected units, since unit_quantity may have been changed - $('input', old_order_article_entry).data('units-expected', <%= - (@order_article.units_billed or @order_article.units_to_order) * - 1.0 * @order_article.article.unit_quantity / @order_article.article_price.unit_quantity - %>); - - // render new element and inject dynamic cell - var new_order_article_entry = $( - '<%= j render(partial: 'edit_amount', locals: {order_article: @order_article}) %>' - ); - - $('td.units_received_cell', new_order_article_entry).replaceWith( - $('td.units_received_cell', old_order_article_entry) - ); - - // finally replace the OrderArticle entry - old_order_article_entry.replaceWith(new_order_article_entry); - - update_delta($('input.units_received', new_order_article_entry)); + $('table#order_articles').parents('form').receiveOrderForm('replace', { + id: <%= @order_article.id %>, + newEntry: $('<%= j render(partial: 'edit_amount', locals: {order_article: @order_article}) %>') + }); })(window); diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index e76db249..da47bea2 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -1,3 +1,4 @@ += render partial: 'shared/js_templates/unit_conversion_popover_template', locals: {set_units_data: true} - title t('.title', name: @order.name) - if current_user.role_finance? || current_user.role_invoices? @@ -95,3 +96,5 @@ = render 'show_js' = render 'shared/articles_by/common', order: @order + + diff --git a/app/views/orders/show.js.erb b/app/views/orders/show.js.erb index 47a69e8d..2c29b7f3 100644 --- a/app/views/orders/show.js.erb +++ b/app/views/orders/show.js.erb @@ -1,4 +1,11 @@ $('#articles_table').html('<%= j render(@partial, order: @order) %>'); +$('*[name="group_order_article[result]"]').each(function() { + $(this).unitConversionField({ + units: unitsData, + popoverTemplate$: $('#unit_conversion_popover_content_template'), + useTargetUnitForStep: false + }); +}); $('.view_buttons a').each(function() { $(this).toggleClass('active', $(this).attr('id') == 'view_<%= j params[:view] %>_btn'); diff --git a/app/views/shared/_article_fields_price.html.haml b/app/views/shared/_article_fields_price.html.haml index ea6ee9a2..efe18036 100644 --- a/app/views/shared/_article_fields_price.html.haml +++ b/app/views/shared/_article_fields_price.html.haml @@ -3,32 +3,22 @@ .input-prepend %span.add-on= t 'number.currency.format.unit' = f.input_field :price, class: 'input-mini' + = f.input :price_unit, as: :select, collection: [], input_html: {'data-initial-value': article.price_unit, class: 'input-medium'}, label: t('articles.form.per'), include_blank: false +.fold-line = f.input :tax do .input-append = f.input_field :tax, class: 'input-mini' %span.add-on % -.fold-line = f.input :deposit do .input-prepend %span.add-on= t 'number.currency.format.unit' = f.input_field :deposit, class: 'input-mini' +.fold-line .control-group %label.control-label{for: 'article_fc_price'} = Article.human_attribute_name(:fc_price) .controls.control-text#article_fc_price - = number_to_currency(f.object.fc_price) rescue nil - --# do this inline, since it's being used in ajax forms only -- field = f.object.class.model_name.to_s.underscore -:javascript - var form = $('#article_fc_price').closest('form'); - $('##{field}_price, ##{field}_tax, ##{field}_deposit', form).on('change keyup', function() { - var price = parseFloat($('##{field}_price', form).val()); - var tax = parseFloat($('##{field}_tax', form).val()); - var deposit = parseFloat($('##{field}_deposit', form).val()); - // Article#gross_price and Article#fc_price - var gross_price = (price + deposit) * (tax / 100 + 1); - var fc_price = gross_price * (#{FoodsoftConfig[:price_markup].to_f} / 100 + 1); - $('#article_fc_price').html($.isNumeric(fc_price) ? I18n.l("currency", fc_price) : '…'); - }); + %span.price_value + %span.price_per_text=t('articles.form.per') + %span.price_unit diff --git a/app/views/shared/_article_fields_units.html.haml b/app/views/shared/_article_fields_units.html.haml index b9704cef..67885336 100644 --- a/app/views/shared/_article_fields_units.html.haml +++ b/app/views/shared/_article_fields_units.html.haml @@ -1,6 +1,33 @@ --# use the local 'f', or supply 'f_uq' and 'f_unit' for more control (like in balancing) +-# var parcel = $($.parseHTML("#{escape_javascript(render('shared/parcel'))}")); +-# var parcels = #{raw @parcels.to_json}; + +.fold-line.d-flex + = f.hidden_field :unit, id: 'article_unit_hidden', value: '' + -# title: unit in which article can be ordered from supplier + - supplier_order_unit_override = article.supplier_order_unit + - supplier_order_unit_override = 'XPP' if supplier_order_unit_override.nil? && article.unit.nil? + = f.input :supplier_order_unit, as: :select, collection: @article_units, selected: supplier_order_unit_override, include_blank: t('.custom_unit'), input_html: {class: 'input-medium'} + -# title: free text unit + = f.input :unit, input_html: {class: 'input-mini ml-1', placeholder: t('.unit')}, label: false + %i.icon-warning-sign{title: t('warn_about_needless_custom_unit')} .fold-line - = (f_uq rescue f).input :unit_quantity, label: Article.human_attribute_name(:unit), - input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit_quantity)} - = (f_unit rescue f).input :unit, label: '×'.html_safe, - input_html: {class: 'input-mini', title: Article.human_attribute_name(:unit)} + .control-group + %label.control-label + = t('.unit_ratios') + %table#fc_base_price{:class => "controls"} + %tbody + - ratios = article.article_unit_ratios + = render :partial => 'shared/article_unit_ratio', :as => 'article_unit_ratio', :collection => ratios, locals: {f: f} + %tfoot + %td{:colspan => 6} + = link_to t('.add_ratio'), '#', 'data-add-ratio' => true, class: 'btn', title: "add ratio" +.fold-line + = f.input :minimum_order_quantity do + .input-append + = f.input_field :minimum_order_quantity, class: 'input-mini' + %span.add-on +.fold-line + = f.input :billing_unit, as: :select, collection: [], input_html: {'data-initial-value': article.billing_unit, class: 'input-medium'}, include_blank: false +.fold-line + = f.input :group_order_granularity, input_html: {class: 'input-mini'} + = f.input :group_order_unit, as: :select, collection: [], input_html: {'data-initial-value': article.group_order_unit, class: 'input-medium'}, label: '×'.html_safe, include_blank: false diff --git a/app/views/shared/_article_unit_ratio.haml b/app/views/shared/_article_unit_ratio.haml new file mode 100644 index 00000000..a4532933 --- /dev/null +++ b/app/views/shared/_article_unit_ratio.haml @@ -0,0 +1,19 @@ +- original_ratio = defined?(original_ratios) ? original_ratios&.to_a&.dig(article_unit_ratio_counter) : nil +- changed = !original_ratio.nil? && (original_ratio.quantity != article_unit_ratio.quantity || original_ratio.unit != article_unit_ratio.unit) +%tr{:class => 'no-indent-left', :style => changed ? "background-color: yellow" : ""} + = f.fields_for(:article_unit_ratios, article_unit_ratio) do |sub_form| + -# title: unit quantifier + %td + -# TODO: Check, if there was any justified reason for this article_unit_ratio_counter logic: + -#= hidden_field f.object.class.to_s.underscore + "[article_unit_ratios_attributes][#{article_unit_ratio_counter + 1}]", :sort, value: article_unit_ratio_counter + 1 + = sub_form.input :sort, as: :hidden + + = sub_form.input :quantity, {input_html: {class: 'input-mini'}, label: false} + %td= '×'.html_safe + %td= sub_form.input :unit, as: :select, include_blank: true, collection: @article_units, label: false, input_html: {class: 'input-medium'} + %td.ml-1= t('.per') + %td + %span.unit_multiplier.mr-1 + %td.actions{style: 'width:1em'} + = link_to t('.remove'), method: :delete, 'data-remove-ratio' => true, title: t('.remove'), class: 'btn btn-danger btn-mini' do + = glyph :remove diff --git a/app/views/shared/_article_price_info.html.haml b/app/views/shared/_article_version_info.html.haml similarity index 100% rename from app/views/shared/_article_price_info.html.haml rename to app/views/shared/_article_version_info.html.haml diff --git a/app/views/shared/articles_by/_article_single.html.haml b/app/views/shared/articles_by/_article_single.html.haml index 6c563d64..6e8aa352 100644 --- a/app/views/shared/articles_by/_article_single.html.haml +++ b/app/views/shared/articles_by/_article_single.html.haml @@ -3,11 +3,9 @@ %tr.list-heading %th{:colspan => "4"}> %h4.name.pull-left - = order_article.article.name + - article_version = order_article.article_version + = article_version.name %small - = "(#{order_article.article.unit}, #{number_to_currency order_article.price.fc_price}" - - pkg_info = pkg_helper(order_article.price) - = ", #{pkg_info}".html_safe unless pkg_info.blank? - ) + = "#{format_billing_unit_with_ratios(article_version)}, #{number_to_currency(article_version.convert_quantity(article_version.fc_price, article_version.billing_unit, article_version.supplier_order_unit))}" - for goa in order_article.group_order_articles.ordered = render 'shared/articles_by/article_single_goa', goa: goa diff --git a/app/views/shared/articles_by/_article_single_goa.html.haml b/app/views/shared/articles_by/_article_single_goa.html.haml index 1f727007..cdade7ab 100644 --- a/app/views/shared/articles_by/_article_single_goa.html.haml +++ b/app/views/shared/articles_by/_article_single_goa.html.haml @@ -1,5 +1,6 @@ %tr{class: if goa.result == 0 then 'unavailable' end, id: "goa_#{goa.id}"} + - article_version = goa.order_article.article_version %td{:style => "width:70%"}= goa.group_order.ordergroup_name - %td.center= "#{goa.quantity} + #{goa.tolerance}" + %td.center= "#{article_version.convert_quantity(goa.quantity, article_version.group_order_unit, article_version.billing_unit)} + #{article_version.convert_quantity(goa.tolerance, article_version.group_order_unit, article_version.billing_unit)}" %td.center.input-delta= group_order_article_edit_result(goa) %td.price{data: {value: goa.total_price}}= number_to_currency(goa.total_price) diff --git a/app/views/shared/articles_by/_articles.html.haml b/app/views/shared/articles_by/_articles.html.haml index c2bb0695..a1e8d44c 100644 --- a/app/views/shared/articles_by/_articles.html.haml +++ b/app/views/shared/articles_by/_articles.html.haml @@ -8,7 +8,7 @@ %acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received' %th= t 'shared.articles_by.price' - - for order_article in order.order_articles.ordered.includes([:article, :article_price]) + - for order_article in order.order_articles.ordered.includes(:article_version) = render 'shared/articles_by/article_single', order_article: order_article %tr %td{colspan: 4} diff --git a/app/views/shared/articles_by/_group_single_goa.html.haml b/app/views/shared/articles_by/_group_single_goa.html.haml index 88189126..59a1b80c 100644 --- a/app/views/shared/articles_by/_group_single_goa.html.haml +++ b/app/views/shared/articles_by/_group_single_goa.html.haml @@ -1,10 +1,10 @@ %tr{class: [cycle('even', 'odd', :name => 'articles'), if goa.result == 0 then 'unavailable' end], id: "goa_#{goa.id}"} - %td.name= goa.order_article.article.name - %td= goa.order_article.article.unit - %td.center= "#{goa.quantity} + #{goa.tolerance}" + - article_version = goa.order_article.article_version + %td.name= goa.order_article.article_version.name + %td= format_billing_unit_with_ratios(article_version) + %td.center= "#{article_version.convert_quantity(goa.quantity, article_version.group_order_unit, article_version.billing_unit)} + #{article_version.convert_quantity(goa.tolerance, article_version.group_order_unit, article_version.billing_unit)}" %td.center.input-delta= group_order_article_edit_result(goa) %td.symbol × - %td= number_to_currency(goa.order_article.price.fc_price) + %td= number_to_currency(article_version.convert_quantity(article_version.fc_price, article_version.billing_unit, article_version.supplier_order_unit)) %td.symbol = %td.price{data: {value: goa.total_price}}= number_to_currency(goa.total_price) - %td= pkg_helper goa.order_article.price diff --git a/app/views/shared/articles_by/_groups.html.haml b/app/views/shared/articles_by/_groups.html.haml index 4bd9c285..8787a503 100644 --- a/app/views/shared/articles_by/_groups.html.haml +++ b/app/views/shared/articles_by/_groups.html.haml @@ -11,7 +11,6 @@ %th= heading_helper Article, :fc_price, short: true %th.symbol %th= t 'shared.articles_by.price' - %th= #heading_helper Article, :unit_quantity, short: true - for group_order in order.group_orders.ordered = render 'shared/articles_by/group_single', group_order: group_order diff --git a/app/views/shared/js_templates/_unit_conversion_popover_template.haml b/app/views/shared/js_templates/_unit_conversion_popover_template.haml new file mode 100644 index 00000000..69274718 --- /dev/null +++ b/app/views/shared/js_templates/_unit_conversion_popover_template.haml @@ -0,0 +1,12 @@ +- if defined?(set_units_data) && set_units_data + - content_for :javascript do + :javascript + const unitsData = #{raw(ArticleUnit.as_hash.to_json)}; +%template#unit_conversion_popover_content_template + %div.popover_contents.text-right{"data-title" => t('helpers.unit_conversion_fields.title')} + .d-flex + %select.unit.mr-1{"data-ignore-onchange" => true} + %input.quantity.input-mini.numeric{:type => 'number', :min => 0} + .conversion-result.mt-1.mb-1 + %input.cancel.btn{:type => 'button', :value => t('helpers.unit_conversion_fields.cancel')} + %input.apply.btn.btn-primary{:type => 'button', :value => t('helpers.unit_conversion_fields.apply')} diff --git a/app/views/stock_takings/_stock_change.html.haml b/app/views/stock_takings/_stock_change.html.haml index c82bd232..313fec66 100644 --- a/app/views/stock_takings/_stock_change.html.haml +++ b/app/views/stock_takings/_stock_change.html.haml @@ -4,6 +4,6 @@ = StockArticle.human_attribute_name :amount (#{stock_change.stock_article.quantity_available}) = form.text_field :quantity, :size => 5, :autocomplete => 'off' - %span{:data => {:toggle => :tooltip, :title => "#{render(:partial => 'shared/article_price_info', :locals => {:article => stock_change.stock_article})}"}} + %span{:data => {:toggle => :tooltip, :title => "#{render(:partial => 'shared/article_version_info', :locals => {:article => stock_change.stock_article})}"}} %b= stock_change.stock_article.name = "(#{number_to_currency(stock_change.stock_article.price)} / #{stock_change.stock_article.unit})" diff --git a/app/views/stockit/_form.html.haml b/app/views/stockit/_form.html.haml index 9048b5a1..83a7b8a7 100644 --- a/app/views/stockit/_form.html.haml +++ b/app/views/stockit/_form.html.haml @@ -1,11 +1,34 @@ -= simple_form_for stock_article, remote: true, :validate => true do |f| += simple_form_for stock_article.latest_article_version, :url => stock_article.new_record? ? stock_articles_path(stock_article, foodcoop: FoodsoftConfig.scope) : stock_article_path(stock_article), remote: true, validate: true do |f| + :javascript + const articleUnitRatioTemplate$ = $($.parseHTML("#{escape_javascript(render(partial: 'shared/article_unit_ratio', locals: {article_unit_ratio: @empty_article_unit_ratio, f: f, article_unit_ratio_counter: -1}))}")); + const units = #{raw(@all_units.to_json)}; + new ArticleForm(articleUnitRatioTemplate$, $('.article-form').parents('form'), units, #{FoodsoftConfig[:price_markup].to_f}); + = render partial: 'shared/js_templates/unit_conversion_popover_template' .modal-header = close_button :modal %h3= title - .modal-body - = f.association :supplier + .modal-body.article-form + = f.hidden_field :id + = f.simple_fields_for :article do |a| + = a.input :supplier_id, collection: Supplier.all, selected: stock_article.supplier_id = f.input :name - = f.input :unit + .fold-line.d-flex + = f.hidden_field :unit, id: 'article_unit_hidden', value: '' + -# title: unit in which article can be ordered from supplier + = f.input :supplier_order_unit, as: :select, collection: @article_units, value: stock_article.supplier_order_unit, include_blank: t('.custom_unit'), input_html: {class: 'input-medium'} + -# title: free text unit + = f.input :unit, input_html: {class: 'input-mini ml-1'}, label: false + .fold-line + .control-group + %label.control-label{for: 'unit_ratios'} + = "Unit ratios" + %table#fc_base_price{:class => "controls"} + %tbody + - ratios = stock_article.article_unit_ratios + = render :partial => 'shared/article_unit_ratio', :as => 'article_unit_ratio', :collection => ratios, locals: {f: f} + %tfoot + %td{:colspan => 6} + = link_to t('.add_ratio'), '#', 'data-add-ratio' => true, class: 'btn', title: "add ratio" = f.input :note - if stock_article.new_record? = f.input :price diff --git a/app/views/stockit/_stock_article.html.haml b/app/views/stockit/_stock_article.html.haml index 4df32342..789fefe9 100644 --- a/app/views/stockit/_stock_article.html.haml +++ b/app/views/stockit/_stock_article.html.haml @@ -3,7 +3,7 @@ %td= stock_article.quantity %td= stock_article.quantity_ordered %td.main_info= stock_article.quantity_available - %td= stock_article.unit + %td= format_supplier_order_unit(stock_article) %td= stock_article.price %td= number_to_percentage stock_article.tax %td= link_to stock_article.supplier.name, stock_article.supplier diff --git a/app/views/supplier_shares/_share_link.html.haml b/app/views/supplier_shares/_share_link.html.haml new file mode 100644 index 00000000..b0fce48c --- /dev/null +++ b/app/views/supplier_shares/_share_link.html.haml @@ -0,0 +1,16 @@ +- if external_uuid.nil? + = t('.not_shared') +- else + - share_url = api_v1_shared_supplier_articles_url(external_uuid) + %div#copy-share-link-button.user-select-none{:role => 'button'} + %span= share_url + %i.icon-copy + + :javascript + document + .getElementById('copy-share-link-button') + .addEventListener('click', () => { + navigator.clipboard.writeText('#{share_url}'); + alert('#{t(".share_link_copied_to_clipboard")}'); + }); + diff --git a/app/views/supplier_shares/update.js.haml b/app/views/supplier_shares/update.js.haml new file mode 100644 index 00000000..caf08dba --- /dev/null +++ b/app/views/supplier_shares/update.js.haml @@ -0,0 +1,2 @@ +$('#share_link').html('#{j(render(partial: 'share_link', locals: {external_uuid: @supplier.external_uuid}))}'); +$('.btn.share').toggle(); diff --git a/app/views/suppliers/_form.html.haml b/app/views/suppliers/_form.html.haml index d8d94d9d..3099b513 100644 --- a/app/views/suppliers/_form.html.haml +++ b/app/views/suppliers/_form.html.haml @@ -1,8 +1,4 @@ = simple_form_for @supplier do |f| - - if @supplier.shared_supplier - .alert.alert-info - = t 'suppliers.shared_supplier_note' - = f.hidden_field :shared_supplier_id = f.input :name = f.input :address = f.input :phone @@ -21,9 +17,9 @@ = f.input :order_howto, as: :text, input_html: {rows: 5} = f.input :note, as: :text, input_html: {rows: 5} = f.input :min_order_quantity + = f.input :supplier_remote_source = render 'shared/custom_form_fields', f: f, type: :supplier - - if @supplier.shared_supplier - = f.input :shared_sync_method, collection: shared_sync_method_collection(@supplier.shared_supplier), input_html: {class: 'input-xlarge'}, include_blank: false, disabled: @supplier.shared_supplier.shared_sync_methods.count < 2 + = f.input :shared_sync_method, collection: shared_sync_method_collection, input_html: {class: 'input-xlarge'}, include_blank: true .form-actions = f.submit class: 'btn' = link_to t('ui.or_cancel'), suppliers_path diff --git a/app/views/suppliers/_import_search_results.haml b/app/views/suppliers/_import_search_results.haml new file mode 100644 index 00000000..cbc6bbc8 --- /dev/null +++ b/app/views/suppliers/_import_search_results.haml @@ -0,0 +1,29 @@ +- if @articles.empty? + %p= t '.not_found' +- else + = pagination_links_remote @pagination + %table.table.table-striped + %thead + %tr + %th= heading_helper Article, :name + %th= heading_helper Article, :origin + %th= heading_helper Article, :manufacturer + %th= heading_helper Article, :note + %th{:style => "width:4em"}= heading_helper Article, :price + %th= heading_helper Article, :supplier_order_unit + %th + %tbody + - @articles.each_with_index do |article, index| + %tr + %td= highlight article[:name], params.fetch(:name, '').split(' ') + %td= article[:origin] + %td= article[:manufacturer] + %td{title: article[:note]}= truncate(article[:note], length: 11) + %td= number_to_currency(article[:price]) + %td= ArticleUnitsLib.get_translated_name_for_code(article[:supplier_order_unit]) + %td.actions.import-actions{:data => {order_number: article[:order_number]}} + - article_exists = @supplier.articles.includes(:latest_article_version).undeleted.where(article_versions: {order_number: article[:order_number]}).exists? + %i.icon-ok{:style => article_exists ? '' : 'display:none;'}= t '.already_imported' + = link_to t('.action_import'), supplier_articles_path(@supplier), + remote: true, class: 'btn btn-small btn-success article_import_btn', data: {index: index}, + style: article_exists ? 'display:none;' : '' diff --git a/app/views/suppliers/index.html.haml b/app/views/suppliers/index.html.haml index 9e4c3870..ab90257f 100644 --- a/app/views/suppliers/index.html.haml +++ b/app/views/suppliers/index.html.haml @@ -2,8 +2,6 @@ - content_for :actionbar do = link_to t('.action_new'), new_supplier_path, class: 'btn btn-primary' - - if FoodsoftConfig[:shared_lists] - = link_to t('.action_import'), shared_suppliers_suppliers_path, class: 'btn' %table.table.table-striped %thead %tr diff --git a/app/views/suppliers/remote_articles.js.haml b/app/views/suppliers/remote_articles.js.haml new file mode 100644 index 00000000..06a72b9f --- /dev/null +++ b/app/views/suppliers/remote_articles.js.haml @@ -0,0 +1,22 @@ +:plain + $('#search_results').html("#{j(render("import_search_results"))}"); + const remoteArticleData = #{raw(@articles.to_json)}; + $("#search_results .article_import_btn").click(function(e) { + e.preventDefault(); + e.stopPropagation(); + const index = $(this).data('index'); + const article = remoteArticleData[index]; + const categoryId = $('#import #article_category_id').children(':selected').val(); + if (categoryId !== '') { + article.article_category_id = categoryId; + } + article.article_unit_ratios_attributes = article.article_unit_ratios; + delete article.article_unit_ratios; + delete article.created_at; + delete article.updated_at; + $.post($(this).attr('href'), {article_version: article}, + function(result) { + alter('success', result); + } + ); + }); diff --git a/app/views/suppliers/shared_suppliers.html.haml b/app/views/suppliers/shared_suppliers.html.haml deleted file mode 100644 index 8e9c3eef..00000000 --- a/app/views/suppliers/shared_suppliers.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -- title t('.title') -= t('.body').html_safe - -%table.table.table-striped - %thead - %tr - %th= heading_helper Supplier, :name - %th= heading_helper Supplier, :address - %th= heading_helper Supplier, :note - %th= heading_helper Supplier, :delivery_days - %th= heading_helper Supplier, :is_subscribed - %tbody - - for shared_supplier in @shared_suppliers - %tr - %td= shared_supplier.name - %td= shared_supplier.address - %td= shared_supplier.note - %td= shared_supplier.delivery_days - %td - - if shared_supplier.suppliers.any? - %i.icon-ok - = associated_supplier_names(shared_supplier) - = link_to t('.subscribe_again'), new_supplier_path(:shared_supplier_id => shared_supplier), class: 'btn' - - else - = link_to t('.subscribe'), new_supplier_path(:shared_supplier_id => shared_supplier), class: 'btn' diff --git a/app/views/suppliers/show.html.haml b/app/views/suppliers/show.html.haml index c3fbe4c4..db506c95 100644 --- a/app/views/suppliers/show.html.haml +++ b/app/views/suppliers/show.html.haml @@ -7,7 +7,7 @@ .row-fluid .span6 - - if shared_supplier = @supplier.shared_supplier + - unless @supplier.shared_sync_method.nil? .alert.alert-info = t 'suppliers.shared_supplier_note' = t "suppliers.shared_supplier_methods.#{@supplier.shared_sync_method}" @@ -43,6 +43,11 @@ %dd= simple_format(@supplier.note) %dt= heading_helper(Supplier, :min_order_quantity) + ':' %dd= @supplier.min_order_quantity + %dt= heading_helper(Supplier, :share_link) + ':' + %dd + %div#share_link= render partial: 'supplier_shares/share_link', locals: { external_uuid: @supplier.external_uuid } + = link_to t('.share'), supplier_share_path(@supplier), remote: true, class: 'btn share', method: :post, style: @supplier.external_uuid.nil? ? nil : 'display: none;' + = link_to t('.remove_share'), supplier_share_path(@supplier), remote: true, class: 'btn share', method: :delete, data: { confirm: t('.confirm_remove_share') }, style: @supplier.external_uuid.nil? ? 'display: none;' : nil .clearfix - if @current_user.role_suppliers? diff --git a/bin/autospec b/bin/autospec index 64dcb9cb..602d5c82 100755 --- a/bin/autospec +++ b/bin/autospec @@ -7,8 +7,8 @@ # require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) require 'rubygems' require 'bundler/setup' diff --git a/bin/mailcatcher b/bin/mailcatcher index a4b2c248..67d7b036 100755 --- a/bin/mailcatcher +++ b/bin/mailcatcher @@ -7,8 +7,8 @@ # require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) require 'rubygems' require 'bundler/setup' diff --git a/bin/resque b/bin/resque index 2e40831b..f018141c 100755 --- a/bin/resque +++ b/bin/resque @@ -7,8 +7,8 @@ # require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) require 'rubygems' require 'bundler/setup' diff --git a/bin/resque-web b/bin/resque-web index b49543cd..22ad07e0 100755 --- a/bin/resque-web +++ b/bin/resque-web @@ -7,8 +7,8 @@ # require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) require 'rubygems' require 'bundler/setup' diff --git a/bin/rspec b/bin/rspec index 0c86b5c6..8bc84617 100755 --- a/bin/rspec +++ b/bin/rspec @@ -7,8 +7,8 @@ # require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) require 'rubygems' require 'bundler/setup' diff --git a/bin/yarn b/bin/yarn index 460dd565..d3627c34 100755 --- a/bin/yarn +++ b/bin/yarn @@ -1,11 +1,9 @@ #!/usr/bin/env ruby APP_ROOT = File.expand_path('..', __dir__) Dir.chdir(APP_ROOT) do - begin - exec "yarnpkg", *ARGV - rescue Errno::ENOENT - $stderr.puts "Yarn executable was not detected in the system." - $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" - exit 1 - end + exec 'yarnpkg', *ARGV +rescue Errno::ENOENT + warn 'Yarn executable was not detected in the system.' + warn 'Download Yarn at https://yarnpkg.com/en/docs/install' + exit 1 end diff --git a/config.ru b/config.ru index e0faca0f..7417cc9a 100644 --- a/config.ru +++ b/config.ru @@ -1,6 +1,6 @@ # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require File.expand_path('config/environment', __dir__) # https://gist.github.com/ebeigarts/5450422 map ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do diff --git a/config/app_config.yml.SAMPLE b/config/app_config.yml.SAMPLE index e43705b6..e0ef9d13 100644 --- a/config/app_config.yml.SAMPLE +++ b/config/app_config.yml.SAMPLE @@ -172,6 +172,15 @@ development: test: <<: *defaults + shared_lists: + adapter: mysql2 + encoding: utf8mb4 + reconnect: false + database: db_shared + pool: 5 + host: mariadb + username: root + password: secret production: <<: *defaults diff --git a/config/application.rb b/config/application.rb index 38c9a6d4..02048377 100644 --- a/config/application.rb +++ b/config/application.rb @@ -28,13 +28,15 @@ class Application < Rails::Application # config.time_zone = 'Central Time (US & Canada)' # Internationalization. - config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '*.yml')] - config.i18n.available_locales = Pathname.glob(Rails.root.join('config', 'locales', '{??,???}{-*,}.yml')).map { |p| p.basename('.yml').to_s } + config.i18n.load_path += Dir[Rails.root.join('config/locales/*.yml')] + config.i18n.available_locales = Pathname.glob(Rails.root.join('config/locales/{??,???}{-*,}.yml')).map do |p| + p.basename('.yml').to_s + end config.i18n.default_locale = :en config.i18n.fallbacks = [:en] # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" + config.encoding = 'utf-8' # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true @@ -44,7 +46,7 @@ class Application < Rails::Application # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql - # TODO Disable this. Uncommenting this line will currently cause rspec to fail. + # TODO: Disable this. Uncommenting this line will currently cause rspec to fail. config.action_controller.permit_all_parameters = true config.active_job.queue_adapter = :resque @@ -76,5 +78,9 @@ class Application < Rails::Application # Foodsoft version VERSION = File.read(Rails.root.join('VERSION')).strip # Current revision, or +nil+ - REVISION = (File.read(Rails.root.join('REVISION')).strip rescue nil) + REVISION = begin + File.read(Rails.root.join('REVISION')).strip + rescue StandardError + nil + end end diff --git a/config/database_shared.yml.MYSQL_SAMPLE b/config/database_shared.yml.MYSQL_SAMPLE new file mode 100644 index 00000000..55d40c13 --- /dev/null +++ b/config/database_shared.yml.MYSQL_SAMPLE @@ -0,0 +1,9 @@ +test: + adapter: mysql2 + encoding: utf8mb4 + reconnect: false + database: test_shared + pool: 5 + host: mariadb + username: root + password: secret diff --git a/config/environments/production.rb b/config/environments/production.rb index 0560b38d..12571fda 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -49,7 +49,7 @@ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = ENV["RAILS_FORCE_SSL"] != "false" + config.force_ssl = ENV['RAILS_FORCE_SSL'] != 'false' # Set to :debug to see everything in the log. config.log_level = :info @@ -89,22 +89,31 @@ config.action_mailer.smtp_settings[:domain] = ENV['SMTP_DOMAIN'] if ENV['SMTP_DOMAIN'].present? config.action_mailer.smtp_settings[:user_name] = ENV['SMTP_USER_NAME'] if ENV['SMTP_USER_NAME'].present? config.action_mailer.smtp_settings[:password] = ENV['SMTP_PASSWORD'] if ENV['SMTP_PASSWORD'].present? - config.action_mailer.smtp_settings[:authentication] = ENV['SMTP_AUTHENTICATION'] if ENV['SMTP_AUTHENTICATION'].present? - config.action_mailer.smtp_settings[:enable_starttls_auto] = ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true' if ENV['SMTP_ENABLE_STARTTLS_AUTO'].present? - config.action_mailer.smtp_settings[:openssl_verify_mode] = ENV['SMTP_OPENSSL_VERIFY_MODE'] if ENV['SMTP_OPENSSL_VERIFY_MODE'].present? + if ENV['SMTP_AUTHENTICATION'].present? + config.action_mailer.smtp_settings[:authentication] = + ENV['SMTP_AUTHENTICATION'] + end + if ENV['SMTP_ENABLE_STARTTLS_AUTO'].present? + config.action_mailer.smtp_settings[:enable_starttls_auto] = + ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true' + end + if ENV['SMTP_OPENSSL_VERIFY_MODE'].present? + config.action_mailer.smtp_settings[:openssl_verify_mode] = + ENV['SMTP_OPENSSL_VERIFY_MODE'] + end else # Use sendmail as default to avoid ssl cert problems config.action_mailer.delivery_method = :sendmail end # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new + config.log_formatter = Logger::Formatter.new # Use a different logger for distributed setups. # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - if ENV["RAILS_LOG_TO_STDOUT"].present? + if ENV['RAILS_LOG_TO_STDOUT'].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) diff --git a/config/i18n-js.yml b/config/i18n-js.yml index 1631dc1b..3f0e650c 100644 --- a/config/i18n-js.yml +++ b/config/i18n-js.yml @@ -7,5 +7,7 @@ translations: '*.number.*', '*.date.formats.*', # foodsoft-specific texts to keep js with normal translations - '*.orders.edit_amount.*' + '*.orders.edit_amount.*', + '*.orders.receive.*', + 'no_articles_available' ] diff --git a/config/initializers/currency_display.rb b/config/initializers/currency_display.rb index 7caa6a64..24ceeb8b 100644 --- a/config/initializers/currency_display.rb +++ b/config/initializers/currency_display.rb @@ -1,7 +1,8 @@ # remove all currency translations, so that we can set the default language and # have it shown in all other languages too -::I18n.available_locales.each do |locale| - unless locale == ::I18n.default_locale - ::I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } }) +I18n.available_locales.each do |locale| + unless locale == I18n.default_locale + I18n.backend.store_translations(locale, + number: { currency: { format: { unit: nil } } }) end end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 958b9e9a..64541987 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -98,7 +98,7 @@ # http://tools.ietf.org/html/rfc6819#section-4.4.2 # http://tools.ietf.org/html/rfc6819#section-4.4.3 # - grant_flows %w(authorization_code implicit password) + grant_flows %w[authorization_code implicit password] # Under some circumstances you might want to have applications auto-approved, # so that the user skips the authorization step. @@ -114,7 +114,7 @@ # my take on https://github.com/doorkeeper-gem/doorkeeper/issues/465 ActiveSupport.on_load(:after_initialize) do - Doorkeeper::ApplicationController.send :include, Concerns::Locale - Doorkeeper::ApplicationController.send :include, Concerns::FoodcoopScope - Doorkeeper::ApplicationController.send :include, Concerns::Auth + Doorkeeper::ApplicationController.include Concerns::Locale + Doorkeeper::ApplicationController.include Concerns::FoodcoopScope + Doorkeeper::ApplicationController.include Concerns::Auth end diff --git a/config/initializers/exception_notification.rb b/config/initializers/exception_notification.rb index 10107865..3d342465 100644 --- a/config/initializers/exception_notification.rb +++ b/config/initializers/exception_notification.rb @@ -14,7 +14,7 @@ # Adds a condition to decide when an exception must be ignored or not. # The ignore_if method can be invoked multiple times to add extra conditions. - config.ignore_if do |exception, options| + config.ignore_if do |_exception, _options| Rails.env.development? || Rails.env.test? end @@ -23,9 +23,9 @@ # Email notifier sends notifications by email. if notification = FoodsoftConfig[:notification] config.add_notifier :email, { - :email_prefix => notification[:email_prefix], - :sender_address => notification[:sender_address], - :exception_recipients => notification[:error_recipients], + email_prefix: notification[:email_prefix], + sender_address: notification[:sender_address], + exception_recipients: notification[:error_recipients] } end diff --git a/config/initializers/extensions.rb b/config/initializers/extensions.rb index 799f52e6..d276aecb 100644 --- a/config/initializers/extensions.rb +++ b/config/initializers/extensions.rb @@ -2,8 +2,8 @@ class String # remove comma from decimal inputs def self.delocalized_decimal(string) - if !string.blank? and string.is_a?(String) - BigDecimal.new(string.sub(',', '.')) + if string.present? and string.is_a?(String) + BigDecimal(string.sub(',', '.')) else string end @@ -13,6 +13,6 @@ def self.delocalized_decimal(string) class Array def cumulative_sum csum = 0 - self.map { |val| csum += val } + map { |val| csum += val } end end diff --git a/config/initializers/rack.rb b/config/initializers/rack.rb index 30970ec9..aa462561 100644 --- a/config/initializers/rack.rb +++ b/config/initializers/rack.rb @@ -1,3 +1,3 @@ # Increase key space for post request. # Warning, this is dangerous. See http://stackoverflow.com/questions/12243694/getting-error-exceeded-available-parameter-key-space -Rack::Utils.key_space_limit = 262144 +Rack::Utils.key_space_limit = 262_144 diff --git a/config/initializers/rails6_backports.rb b/config/initializers/rails6_backports.rb index b72f4220..d56f8eee 100644 --- a/config/initializers/rails6_backports.rb +++ b/config/initializers/rails6_backports.rb @@ -1,6 +1,6 @@ raise "Remove no-longer-needed #{__FILE__}!" if Rails::VERSION::MAJOR >= 6 -require "weakref" +require 'weakref' module ActiveRecord # Backport https://github.com/rails/rails/pull/36998 and https://github.com/rails/rails/pull/36999 @@ -15,9 +15,7 @@ class Reaper class << self def register_pool(pool, frequency) # :nodoc: @mutex.synchronize do - unless @threads[frequency]&.alive? - @threads[frequency] = spawn_thread(frequency) - end + @threads[frequency] = spawn_thread(frequency) unless @threads[frequency]&.alive? @pools[frequency] ||= [] @pools[frequency] << WeakRef.new(pool) end diff --git a/config/initializers/ruby_units.rb b/config/initializers/ruby_units.rb index b8b56cca..af422fcb 100644 --- a/config/initializers/ruby_units.rb +++ b/config/initializers/ruby_units.rb @@ -2,28 +2,28 @@ if defined? RubyUnits RubyUnits::Unit.redefine!('liter') do |unit| - unit.aliases += %w{ltr} + unit.aliases += %w[ltr] end RubyUnits::Unit.redefine!('kilogram') do |unit| - unit.aliases += %w{KG} + unit.aliases += %w[KG] end RubyUnits::Unit.redefine!('gram') do |unit| - unit.aliases += %w{gr} + unit.aliases += %w[gr] end RubyUnits::Unit.define('piece') do |unit| unit.definition = RubyUnits::Unit.new('1 each') - unit.aliases = %w{pc pcs piece pieces} # locale: en - unit.aliases += %w{st stuk stuks} # locale: nl + unit.aliases = %w[pc pcs piece pieces] # locale: en + unit.aliases += %w[st stuk stuks] # locale: nl unit.kind = :counting end RubyUnits::Unit.define('bag') do |unit| unit.definition = RubyUnits::Unit.new('1 each') - unit.aliases = %w{bag bags blt sachet sachets} # locale: en - unit.aliases += %w{zak zakken zakje zakjes} # locale: nl + unit.aliases = %w[bag bags blt sachet sachets] # locale: en + unit.aliases += %w[zak zakken zakje zakjes] # locale: nl unit.kind = :counting end diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 14054341..142c4849 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -5,20 +5,20 @@ # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. Foodsoft::Application.config.secret_key_base = begin - if (token = ENV['SECRET_KEY_BASE']).present? + if (token = ENV.fetch('SECRET_KEY_BASE', nil)).present? token elsif Rails.env.production? || Rails.env.staging? - raise "You must set SECRET_KEY_BASE" + raise 'You must set SECRET_KEY_BASE' elsif Rails.env.test? SecureRandom.hex(30) # doesn't really matter else - sf = Rails.root.join('tmp', 'secret_key_base') - if File.exists?(sf) + sf = Rails.root.join('tmp/secret_key_base') + if File.exist?(sf) File.read(sf) else puts "=> Generating initial SECRET_KEY_BASE in #{sf}" token = SecureRandom.hex(30) - File.open(sf, 'w') { |f| f.write(token) } + File.write(sf, token) token end end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index d7841180..370a202e 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -3,7 +3,7 @@ module ActionDispatch module Session class SlugCookieStore < CookieStore - alias_method :orig_set_cookie, :set_cookie + alias orig_set_cookie set_cookie def set_cookie(request, session_id, cookie) if script_name = FoodsoftConfig[:script_name] diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb index a95278d0..1292ac83 100644 --- a/config/initializers/simple_form_bootstrap.rb +++ b/config/initializers/simple_form_bootstrap.rb @@ -11,7 +11,7 @@ end end - config.wrappers :prepend, tag: 'div', class: "control-group", error_class: 'error' do |b| + config.wrappers :prepend, tag: 'div', class: 'control-group', error_class: 'error' do |b| b.use :html5 b.use :placeholder b.use :label @@ -24,7 +24,7 @@ end end - config.wrappers :append, tag: 'div', class: "control-group", error_class: 'error' do |b| + config.wrappers :append, tag: 'div', class: 'control-group', error_class: 'error' do |b| b.use :html5 b.use :placeholder b.use :label diff --git a/config/locales/de.yml b/config/locales/de.yml index 2d96146c..0bedc246 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -25,14 +25,33 @@ de: unit_quantity: Gebindegröße unit_quantity_short: GebGr units: Gebinde + article_unit: + code: Einheitencode gemäß Empfehlung der United Nations Economic Commission (UNECE) + code_short: Code + symbol: Einheitenzeichen + description: Beschreibung (Englisch) article_category: description: Beschreibung name: Name - article_price: + article_version: deposit: Pfand price: Nettopreis tax: MwSt unit_quantity: Gebindegröße + + availability: Artikel ist verfügbar? + name: Name + note: Notiz + origin: Herkunft + manufacturer: Produzent + order_number: Bestellnummer + article_category: Kategorie + + supplier_order_unit: Liefereinheit + group_order_granularity: bestellbar per + billing_unit: Abrechnungseinheit + price_unit: Preiseinheit + minimum_order_quantity: Mindestbestellmenge bank_account: balance: Kontostand bank_gateway: Bankgateway @@ -249,6 +268,7 @@ de: exclusion: erledigte Aufgaben können nicht wöchentlich wiederholt werden models: article: Artikel + article_version: Artikel article_category: Kategorie bank_account: Bankkonto bank_gateway: Bankgateway @@ -484,6 +504,8 @@ de: notice_unavail: Alle gewählten Artikel wurden auf "nicht verfügbar" gesetzt update_sync: notice: Alle Artikel und Preise wurden aktualisiert + complete_units_migration: + notice: Artikel erfolgreich migriert destroy_active_article: drop: entfernen note: "%{article} wird in laufenden Bestellungen verwendet und kann nicht gelöscht werden. Bitte zuerst den Artikel aus den Bestellungen %{drop_link}." @@ -503,6 +525,7 @@ de: change_supplier: Lieferant wechseln ... download: Artikel herunterladen edit_all: Alle bearbeiten + migrate_units: Alte Artikeleinheiten migrieren ext_db: import: Suchen/Importieren sync: Synchronisieren @@ -516,6 +539,24 @@ de: search_placeholder: Name ... title: Artikel von %{supplier} (%{count}) upload: Artikel hochladen + prepare_units_migration: + title: Artikel von %{supplier} migrieren + explanation: | + TODO: Allgemeine Erklärung + Es wird sehr empfohlen ein Backup herunterzuladen ehe Sie fortfahren: + download: Artikel-Backup herunterladen + migrate_units: Nächster Schritt + migrate_units: + title: Artikel von %{supplier} migrieren + explanation: | + Die Artikeleinheiten werden nun in eine neue, flexiblere Form konvertiert. Passen Sie die Konvertierungsvorschläge an, ehe sie fortfahren. + TODO: Besser erklären; vielleicht mit Doku verlinken? + column_headers: + apply: Konvertieren + original_plaintext_unit: Ursprüngliche Volltext-Einheit + affected_articles: Betroffene Artikel + contains: Beinhaltet + run_migration: Migration jetzt durchführen model: error_in_use: "%{article} kann nicht gelöscht werden. Der Artikel befindet sich in einer laufenden Bestellung!" error_nosel: Du hast keine Artikel ausgewählt @@ -763,6 +804,7 @@ de: not_found: text: Diese Seite existiert anscheinend nicht. Entschuldigung! title: Seite nicht gefunden + step_error: Gib eine Zahl >=%{min} ein, die ein Vielfaches von %{granularity} ist. feedback: create: notice: Das Feedback wurde erfolgreich verschickt. Vielen Dank! @@ -1118,6 +1160,10 @@ de: create: Einladung verschicken tasks: required_users: "Es fehlen %{count} Mitstreiterinnen!" + unit_conversion_fields: + title: Einheiten umrechnen + cancel: Abbrechen + apply: Anwenden home: apple_bar: desc: 'Abgebildet ist das Verhältnis von erledigten Aufgaben zu dem Bestellvolumen Deiner Bestellgruppe im Vergleich zum Durchschnitt in der Foodcoop. Konkret: Pro %{amount} Bestellsumme solltest Du eine Aufgabe machen!' @@ -1406,6 +1452,7 @@ de: stock: Lager suppliers: Lieferanten/Artikel title: Artikel + article_units: Artikeleinheiten finances: accounts: Konten verwalten balancing: Bestellungen abrechnen @@ -1579,6 +1626,14 @@ de: ordered_desc: Anzahl der Artikel die durch das Mitglied bestellt wurden (Menge + Toleranz) received: Bekommen received_desc: Anzahl der Artikel die ein Mitglied erhält + article_fields_units: + custom_unit: Benutzerdefiniert + unit: Einheit + unit_ratios: Einheiten-Umrechnung + add_ratio: Umrechnung hinzufügen + article_unit_ratio: + remove: Umrechnung entfernen + per: pro articles_by: price: Gesamtpreis price_sum: Summe @@ -1736,6 +1791,20 @@ de: stock_changes: Verlauf des Lagerbestands update: notice: Lagerartikel »%{name}« aktualisiert. + article_units: + index: + search: Einheit/Symbol suchen + rows: + request_translation: Für diese Einheit existiert noch keine Übersetzung. Wenn Sie der Meinung sind, dass es eine gebräuchliche Einheit ist, klicken Sie hier um ein neues GitHub Issue zu erstellen. + create_link: + add: Hinzufügen + destroy_link: + delete: Entfernen + confirm: Wollen Sie die Artikeleinheit wirklich entfernen? (Bestehende Artikel, die diese Einheit verwenden, werden nicht verändert, aber man kann die Einheit dann nicht mehr in neuen Artikeln verwenden.) + create: + success: Artikeleinheit %{name} erfolgreich hinzugefügt. + destroy: + success: Artikeleinheit %{name} erfolgreich entfernt. suppliers: create: notice: Lieferant wurde erstellt diff --git a/config/locales/en.yml b/config/locales/en.yml index d94c7204..79caf1df 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -25,14 +25,35 @@ en: unit_quantity: Unit quantity unit_quantity_short: U.Q. units: Units + supplier_order_unit: Supplier order unit + article_unit: + code: Unit code according to the recommendation of the United Nations Economic Commission (UNECE) + code_short: Code + symbol: Symbol + description: Description article_category: description: Import names name: Name - article_price: + article_version: deposit: Deposit price: Price (net) tax: VAT unit_quantity: Unit quantity + + + availability: Is article available? + name: Name + note: Note + origin: Origin + manufacturer: Manufacturer + order_number: Order number + article_category: Category + + supplier_order_unit: Delivery unit + group_order_granularity: can be ordered per + billing_unit: Billing unit + price_unit: Price unit + minimum_order_quantity: Minimum order quantity bank_account: balance: Balance bank_gateway: Bank gateway @@ -138,6 +159,7 @@ en: updated_by: Last edited by order_article: article: Article + article_version: Article missing_units: Missing units missing_units_short: Missing quantity: Desired amount @@ -485,6 +507,8 @@ en: notice_unavail: All selected articles were set to be unavailable. update_sync: notice: All articles and prices were updated. + complete_units_migration: + notice: Articles successfully migrated destroy_active_article: drop: delete note: "%{article} is used in current orders and can not be deleted Please first ... the article from orders %{drop_link}." @@ -496,14 +520,12 @@ en: form: title_edit: Edit article title_new: Add new article - import_search_results: - action_import: import - already_imported: imported - not_found: No articles found + per: per index: change_supplier: Change supplier ... download: Download articles edit_all: Edit all + migrate_units: Migrate old article units ext_db: import: Import article sync: Synchronise @@ -517,6 +539,23 @@ en: search_placeholder: Name ... title: Articles from %{supplier} (%{count}) upload: Upload articles + prepare_units_migration: + explanation: | + TODO: General explanation + It's highly recommended to download a backup before continuing: + title: Migrate articles of %{supplier} + download: Download articles backup + migrate_units: + title: Migrate articles of %{supplier} + explanation: | + This will try to migrate articles to their new, more versatile format. You may adapt the suggested changes before continuing if you see fit. + TODO: Expand on this explanation - maybe link to some docs + column_headers: + apply: Convert + original_plaintext_unit: Original plaintext unit + affected_articles: Affected articles + contains: Contains + run_migration: Run migration now model: error_in_use: "%{article} can not be deleted because the article is part of a current order!" error_nosel: You have selected no articles @@ -765,6 +804,7 @@ en: not_found: text: This page does not appear to exist, sorry! title: Page not found + step_error: Enter a number >=%{min}, which is a multiple of %{granularity}. feedback: create: notice: Your feedback was sent successfully. Thanks a lot! @@ -1121,6 +1161,10 @@ en: tasks: required_users: "%{count} members are still needed!" task_title: "%{name} (%{duration}h)" + unit_conversion_fields: + title: Convert units + cancel: Cancel + apply: Apply home: apple_bar: desc: 'This shows the proportion of completed tasks to the volume of orders for your ordergroup in comparison to the average of the Foodcoop. In practice: for every %{amount} of total orders, you should execute a task!' @@ -1414,6 +1458,7 @@ en: stock: Stock suppliers: Suppliers/articles title: Articles + article_units: Article units dashboard: Dashboard finances: accounts: Manage accounts @@ -1592,6 +1637,14 @@ en: ordered_desc: Number of articles as ordered by member (amount + tolerance) received: Received received_desc: Number of articles that (will be) received by member + article_fields_units: + custom_unit: Custom + unit: Unit + unit_ratios: Unit ratios + add_ratio: Add ratio + article_unit_ratio: + remove: Remove ratio + per: per articles_by: price: Total price price_sum: Sum @@ -1637,8 +1690,8 @@ en: error_notification: default_message: Errors were found. Please check the form. hints: - article: - unit: e.g. KG or 1L or 500g + # article: + # unit: e.g. KG or 1L or 500g article_category: description: comma-separated list of category names recognised at import/synchronisation order_article: @@ -1788,8 +1841,19 @@ en: last_orders: Last orders new_delivery: New delivery show_deliveries: Show all + share: Share + remove_share: Remove share + confirm_remove_share: Do you really want to stop sharing this supplier's articles? (If you later decide to start sharing them again, you'll have to resend the new link to all subscribers who you think should have access.) update: notice: Supplier was updated + import_search_results: + action_import: import + already_imported: imported + not_found: No articles found + supplier_shares: + share_link: + not_shared: Supplier's articles are currently not shared with anyone. + share_link_copied_to_clipboard: Share link has been copied to the clipboard. tasks: accept: notice: You have accepted the task diff --git a/config/locales/es.yml b/config/locales/es.yml index 620ec3bb..ca4de0be 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -28,7 +28,7 @@ es: article_category: description: Importar nombres name: Nombre - article_price: + article_version: deposit: Depósito price: Precio (neto) tax: IVA @@ -268,7 +268,7 @@ es: all_ordergroups: Todos los grupos de pedido all_users: Todos los usuarios all_workgroups: Todos los grupos de trabajo - created_at: creado + created_at: creado first_paragraph: Aquí puedes administrar grupos y usuarios de Foodsoft. groupname: nombre del grupo members: miembros @@ -513,7 +513,7 @@ es: status: Estado (x=saltar) file_label: Por favor elige un archivo compatible options: - convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar). + convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar). outlist_absent: Borrar artículos que no están en el archivo subido. sample: juices: Jugos @@ -1014,7 +1014,7 @@ es: changes_saved: Guarda los cambios. index: my_ordergroup: - last_update: La última actualización fue hace %{when} + last_update: La última actualización fue hace %{when} title: Mi grupo de pedido transactions: title: Últimas transacciones @@ -1103,7 +1103,7 @@ es: error_invite_invalid: Tu invitación no es válida. error_token_invalid: La sesión ha expirado o no es válida. Prueba de nuevo. reset_password: - notice: Si tu email está ya registrado aquí, recibirás un mensaje con un enlace para + notice: Si tu email está ya registrado aquí, recibirás un mensaje con un enlace para update_password: notice: Tu contraseña ha sido actualizada. Prueba a conectarte ahora. forgot_password: @@ -1469,7 +1469,7 @@ es: copy: title: Copia artículo de stock create: - notice: Se ha creado el nuevo producto en stock "%{name}" + notice: Se ha creado el nuevo producto en stock "%{name}" derive: title: Añade un artículo en stock desde plantilla destroy: @@ -1577,7 +1577,7 @@ es: accept_task: Aceptar tarea confirm_delete_group: Estás seguro/a de que quieres borrar esta tarea y todas las tareas subsecuentes? confirm_delete_single: Estás seguro/a de que quieres borrar esta tarea? - confirm_delete_single_from_group: Estás seguro/a de que quieres borrar esta tarea (y mantener las tareas recurrentes relacionadas)? + confirm_delete_single_from_group: Estás seguro/a de que quieres borrar esta tarea (y mantener las tareas recurrentes relacionadas)? delete_group: Borrar esta tarea y las subsecuentes edit_group: Edita recurrencia mark_done: Marca tarea como hecha diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4dbdb864..d029b380 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -14,7 +14,7 @@ fr: gross_price: Prix TTC manufacturer: Product-rice-eur name: Nom - order_number: Numéro + order_number: Numéro order_number_short: Numéro origin: Lieu de production price: Prix HT @@ -27,7 +27,7 @@ fr: article_category: description: Description name: Nom - article_price: + article_version: deposit: Consigne price: Prix HT tax: TVA @@ -860,7 +860,7 @@ fr: error_invite_invalid: Ton invitation n'est pas ou plus valide. error_token_invalid: Ton jeton de connexion n'est pas ou plus valide, essaie de cliquer à nouveau sur le lien. reset_password: - notice: Tu vas maintenant recevoir un message contenant un lien qui te permettra de réinitialiser ton mot de passe. + notice: Tu vas maintenant recevoir un message contenant un lien qui te permettra de réinitialiser ton mot de passe. update_password: notice: Ton mot de passe a été mis à jour. Tu peux maintenant de connecter. forgot_password: @@ -1093,7 +1093,7 @@ fr: closed: décomptée finished: clôturée open: en cours - received: reçu + received: reçu update: notice: La commande a été mise à jour. update_order_amounts: @@ -1344,7 +1344,7 @@ fr: notice: La description du boulot a été mise à jour. notice_converted: Le boulot a été converti en boulot ordinaire (sans répétition). user: - more: Tu t'ennuies en ce moment? Il y aura sûrement du boulot pour toi %{tasks_link}. + more: Tu t'ennuies en ce moment? Il y aura sûrement du boulot pour toi %{tasks_link}. tasks_link: par là-bas title: Ton boulot title_accepted: Boulots acceptés @@ -1369,7 +1369,7 @@ fr: edit: title: Modifier l'équipe error_last_admin_group: Impossible de supprimer la dernière cellule avec privilèges administratrices. - error_last_admin_role: Les privilèges administratrices ne peuvent pas être retirés à la dernière cellule qui les possède. + error_last_admin_role: Les privilèges administratrices ne peuvent pas être retirés à la dernière cellule qui les possède. index: title: Équipes update: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 4c97dda4..698523aa 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -28,7 +28,7 @@ nl: article_category: description: Import namen name: Naam - article_price: + article_version: deposit: Statiegeld price: Netto prijs tax: BTW diff --git a/config/navigation.rb b/config/navigation.rb index c8b7d088..c671c18c 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -21,31 +21,43 @@ primary.item :orders, I18n.t('navigation.orders.title'), '#' do |subnav| subnav.item :ordering, I18n.t('navigation.orders.ordering'), group_orders_path subnav.item :ordering_archive, I18n.t('navigation.orders.archive'), archive_group_orders_path - subnav.item :orders, I18n.t('navigation.orders.manage'), orders_path, if: Proc.new { current_user.role_orders? } - subnav.item :pickups, I18n.t('navigation.orders.pickups'), pickups_path, if: Proc.new { current_user.role_pickups? } + subnav.item :orders, I18n.t('navigation.orders.manage'), orders_path, if: proc { current_user.role_orders? } + subnav.item :pickups, I18n.t('navigation.orders.pickups'), pickups_path, if: proc { current_user.role_pickups? } end primary.item :articles, I18n.t('navigation.articles.title'), '#', - if: Proc.new { current_user.role_article_meta? or current_user.role_suppliers? } do |subnav| + if: proc { current_user.role_article_meta? or current_user.role_suppliers? } do |subnav| subnav.item :suppliers, I18n.t('navigation.articles.suppliers'), suppliers_path subnav.item :stockit, I18n.t('navigation.articles.stock'), stock_articles_path subnav.item :categories, I18n.t('navigation.articles.categories'), article_categories_path + subnav.item :article_units, I18n.t('navigation.articles.article_units'), article_units_path end - primary.item :finance, I18n.t('navigation.finances.title'), '#', if: Proc.new { current_user.role_finance? || current_user.role_invoices? } do |subnav| - subnav.item :finance_home, I18n.t('navigation.finances.home'), finance_root_path, if: Proc.new { current_user.role_finance? } - subnav.item :finance_home, I18n.t('navigation.finances.bank_accounts'), finance_bank_accounts_path, if: Proc.new { current_user.role_finance? } - subnav.item :accounts, I18n.t('navigation.finances.accounts'), finance_ordergroups_path, if: Proc.new { current_user.role_finance? } - subnav.item :balancing, I18n.t('navigation.finances.balancing'), finance_order_index_path, if: Proc.new { current_user.role_finance? } + primary.item :finance, I18n.t('navigation.finances.title'), '#', if: proc { + current_user.role_finance? || current_user.role_invoices? + } do |subnav| + subnav.item :finance_home, I18n.t('navigation.finances.home'), finance_root_path, if: proc { + current_user.role_finance? + } + subnav.item :finance_home, I18n.t('navigation.finances.bank_accounts'), finance_bank_accounts_path, if: proc { + current_user.role_finance? + } + subnav.item :accounts, I18n.t('navigation.finances.accounts'), finance_ordergroups_path, if: proc { + current_user.role_finance? + } + subnav.item :balancing, I18n.t('navigation.finances.balancing'), finance_order_index_path, if: proc { + current_user.role_finance? + } subnav.item :invoices, I18n.t('navigation.finances.invoices'), finance_invoices_path end - primary.item :admin, I18n.t('navigation.admin.title'), '#', if: Proc.new { current_user.role_admin? } do |subnav| + primary.item :admin, I18n.t('navigation.admin.title'), '#', if: proc { current_user.role_admin? } do |subnav| subnav.item :admin_home, I18n.t('navigation.admin.home'), admin_root_path subnav.item :users, I18n.t('navigation.admin.users'), admin_users_path subnav.item :ordergroups, I18n.t('navigation.admin.ordergroups'), admin_ordergroups_path subnav.item :workgroups, I18n.t('navigation.admin.workgroups'), admin_workgroups_path - subnav.item :mail_delivery_status, I18n.t('navigation.admin.mail_delivery_status'), admin_mail_delivery_status_index_path + subnav.item :mail_delivery_status, I18n.t('navigation.admin.mail_delivery_status'), + admin_mail_delivery_status_index_path subnav.item :finances, I18n.t('navigation.admin.finance'), admin_finances_path subnav.item :config, I18n.t('navigation.admin.config'), admin_config_path end diff --git a/config/puma.rb b/config/puma.rb index c341524a..a7a3c1d3 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -4,16 +4,16 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 1 } +threads_count = ENV.fetch('RAILS_MAX_THREADS') { 1 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT') { 3000 } # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV') { 'development' } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together @@ -21,7 +21,7 @@ # Workers do not work on JRuby or Windows (both of which do not support # processes). # -workers ENV.fetch("WEB_CONCURRENCY") { 8 } +workers ENV.fetch('WEB_CONCURRENCY') { 8 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code @@ -53,3 +53,8 @@ # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart + +if ENV.fetch('RAILS_ENV') == 'development' + Rails.logger.info 'development => worker_timeout 3600' + worker_timeout 3600 +end diff --git a/config/routes.rb b/config/routes.rb index d1aa8886..4f54918b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,9 @@ Rails.application.routes.draw do - get "order_comments/new" + get 'order_comments/new' - get "comments/new" + get 'comments/new' - get "sessions/new" + get 'sessions/new' root to: 'sessions#redirect_to_foodcoop', as: nil @@ -21,8 +21,8 @@ post '/login/reset_password' => 'login#reset_password', as: :reset_password get '/login/new_password' => 'login#new_password', as: :new_password patch '/login/update_password' => 'login#update_password', as: :update_password - match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation, via: [:get, :post] - resources :sessions, only: [:new, :create, :destroy] + match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation, via: %i[get post] + resources :sessions, only: %i[new create destroy] get '/foodcoop.css' => 'styles#foodcoop', as: 'foodcoop_css' @@ -62,11 +62,11 @@ resources :group_order_articles - resources :order_comments, only: [:new, :create] + resources :order_comments, only: %i[new create] ############ Foodcoop orga - resources :invites, only: [:new, :create] + resources :invites, only: %i[new create] resources :tasks do collection do @@ -88,7 +88,7 @@ resources :ordergroups, only: [:index] - resources :workgroups, only: [:index, :edit, :update] + resources :workgroups, only: %i[index edit update] end ########### Article management @@ -111,8 +111,15 @@ end end + resources :article_units do + collection do + get :search + end + end + resources :suppliers do - get :shared_suppliers, on: :collection + get :remote_articles + resource :share, only: %i[create destroy], controller: :supplier_shares resources :deliveries do collection do @@ -128,12 +135,13 @@ collection do post :update_selected get :edit_all + get :prepare_units_migration + get :migrate_units + post :complete_units_migration post :update_all get :upload post :parse_upload post :create_from_upload - get :shared - get :import post :sync post :update_synchronized end @@ -172,7 +180,7 @@ get :unpaid, on: :collection end - resources :links, controller: 'financial_links', only: [:create, :show] do + resources :links, controller: 'financial_links', only: %i[create show] do collection do get :incomplete end @@ -182,8 +190,10 @@ delete 'bank_transactions/:bank_transaction', action: 'remove_bank_transaction', as: 'remove_bank_transaction' get :index_financial_transaction - put 'financial_transactions/:financial_transaction', action: 'add_financial_transaction', as: 'add_financial_transaction' - delete 'financial_transactions/:financial_transaction', action: 'remove_financial_transaction', as: 'remove_financial_transaction' + put 'financial_transactions/:financial_transaction', action: 'add_financial_transaction', + as: 'add_financial_transaction' + delete 'financial_transactions/:financial_transaction', action: 'remove_financial_transaction', + as: 'remove_financial_transaction' get :index_invoice put 'invoices/:invoice', action: 'add_invoice', as: 'add_invoice' @@ -197,12 +207,14 @@ resources :ordergroups, only: [:index] do resources :financial_transactions, as: :transactions end - resources :financial_transactions, as: :foodcoop_financial_transactions, path: 'foodcoop/financial_transactions', only: [:index, :new, :create] + resources :financial_transactions, as: :foodcoop_financial_transactions, path: 'foodcoop/financial_transactions', + only: %i[index new create] get :transactions, controller: :financial_transactions, action: :index_collection delete 'transactions/:id', controller: :financial_transactions, action: :destroy, as: :transaction get 'transactions/new_collection' => 'financial_transactions#new_collection', as: 'new_transaction_collection' - post 'transactions/create_collection' => 'financial_transactions#create_collection', as: 'create_transaction_collection' + post 'transactions/create_collection' => 'financial_transactions#create_collection', + as: 'create_transaction_collection' resources :bank_accounts, only: [:index] do member do @@ -214,7 +226,7 @@ resources :bank_transactions, as: :transactions end - resources :bank_transactions, only: [:index, :show] + resources :bank_transactions, only: %i[index show] end ########### Administration @@ -246,11 +258,11 @@ get :memberships, on: :member end - resources :mail_delivery_status, only: [:index, :show, :destroy] do + resources :mail_delivery_status, only: %i[index show destroy] do delete :index, on: :collection, action: :destroy_all end - resource :config, only: [:show, :update] do + resource :config, only: %i[show update] do get :list end end @@ -265,23 +277,27 @@ namespace :user do root to: 'users#show' get :financial_overview, to: 'ordergroup#financial_overview' - resources :financial_transactions, only: [:index, :show, :create] + resources :financial_transactions, only: %i[index show create] resources :group_order_articles end - resources :financial_transaction_classes, only: [:index, :show] - resources :financial_transaction_types, only: [:index, :show] - resources :financial_transactions, only: [:index, :show] - resources :orders, only: [:index, :show] - resources :order_articles, only: [:index, :show] + resources :financial_transaction_classes, only: %i[index show] + resources :financial_transaction_types, only: %i[index show] + resources :financial_transactions, only: %i[index show] + resources :orders, only: %i[index show] + resources :order_articles, only: %i[index show] resources :group_order_articles - resources :article_categories, only: [:index, :show] + resources :article_categories, only: %i[index show] + + resources :shared_suppliers, param: :uuid, only: [] do + resources :articles, only: [:index] + end end end ############## Feedback - resource :feedback, only: [:new, :create], controller: 'feedback' + resource :feedback, only: %i[new create], controller: 'feedback' ############## The rest diff --git a/config/schedule.rb b/config/schedule.rb index f22c1348..72e3cbcc 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -2,22 +2,22 @@ # Learn more: http://github.com/javan/whenever # Upcoming tasks notifier -every :day, :at => '7:20 am' do - rake "multicoops:run TASK=foodsoft:notify_upcoming_tasks" - rake "multicoops:run TASK=foodsoft:notify_users_of_weekly_task" +every :day, at: '7:20 am' do + rake 'multicoops:run TASK=foodsoft:notify_upcoming_tasks' + rake 'multicoops:run TASK=foodsoft:notify_users_of_weekly_task' end # Import and assign bank transactions -every :weekday, :at => %w(5:56am 6:04pm) do - rake "multicoops:run TASK=foodsoft:import_and_assign_bank_transactions" +every :weekday, at: %w[5:56am 6:04pm] do + rake 'multicoops:run TASK=foodsoft:import_and_assign_bank_transactions' end # Weekly taks -every :sunday, :at => '7:14 am' do - rake "multicoops:run TASK=foodsoft:create_upcoming_periodic_tasks" +every :sunday, at: '7:14 am' do + rake 'multicoops:run TASK=foodsoft:create_upcoming_periodic_tasks' end # Finish ended orders every 1.minute do - rake "multicoops:run TASK=foodsoft:finish_ended_orders" + rake 'multicoops:run TASK=foodsoft:finish_ended_orders' end diff --git a/config/spring.rb b/config/spring.rb index c9119b40..9fa7863f 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,6 +1,6 @@ -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/config/units-of-measure/locales/unece_de.yml b/config/units-of-measure/locales/unece_de.yml new file mode 100644 index 00000000..2719abfb --- /dev/null +++ b/config/units-of-measure/locales/unece_de.yml @@ -0,0 +1,763 @@ +# This file has been autogenerated by `rake units:build_i18n_from_csv` +--- +unece_units: + XCU: + name: Becher + XCN: + name: Behälter + XSH: + name: Beutel + X43: + name: Bigbag + XST: + name: Blatt + abbreviation: Bl. + XOK: + name: Block + XVA: + name: Bottich + XBX: + name: Box + XBH: + name: Bund + abbreviation: Bd. + aliases: + - bd + XBE: + name: Bündel + abbreviation: Bdl. + XCX: + name: Dose + XBJ: + name: Eimer + XUN: + name: Einheit + XOS: + name: Einweg-Palette + XDH: + name: Eurobox, CHEP-Box + abbreviation: Box + XBA: + name: Fass + XFI: + name: Fässchen + XBO: + name: Flasche + abbreviation: Fl. + aliases: + - fl + XBQ: + name: Flasche in Umverpackung + abbreviation: Fl. + XFB: + name: Flexibag + XFT: + name: Foodtainer + XJR: + name: Glas + abbreviation: Gl. + aliases: + - gl + XGR: + name: Glasgefäß + XOW: + name: Großer Sack in Palettengröße + X8B: + name: Holzkiste + XCV: + name: Hülle + XWA: + name: Intermediate Bulk Container (IBC) + abbreviation: IBC + XEI: + name: Isotherme Box + XJT: + name: Jutebeutel + XGY: + name: Jutesack + XJY: + name: Kanister + XBD: + name: Karton + XCR: + name: Kiste + XAI: + name: Klappverpackung + XPA: + name: Kollo + XBK: + name: Korb + XBI: + name: Kübel + XOV: + name: Mehrwegpalette + XNT: + name: Netz + XPK: + name: Packung + abbreviation: Pkg. + aliases: + - pkg + - pkt + XPC: + name: Paket + XPX: + name: Palette + X5M: + name: Papiertüte + XPR: + name: Plastikgefäß + XEC: + name: Plastiktüte + X6H: + name: Plastik-Verbundverpackung + X44: + name: Polybeutel + XBR: + name: Riegel + XCW: + name: Rollcontainer + XBT: + name: Rolle + XSA: + name: Sack + XBM: + name: Schüssel + XSX: + name: Set + XDN: + name: Spender + XAE: + name: Sprühdose + XSC: + name: Steige + XLU: + name: Stiege + X5L: + name: Stofftüte + XPP: + name: Stück + aliases: + - stk + - st + - stueck + XPU: + name: Schale + XBG: + name: Tasche + XP2: + name: Tiegel + XCK: + name: Tonne + XPT: + name: Topf + aliases: + - tiegel + XGI: + name: Träger + XTU: + name: Tube + PTN: + name: Portion + long_name: Portion + symbols: [] + STC: + name: Stange + long_name: Stange + symbols: [] + DMT: + name: Dezimeter + long_name: Dezimeter + symbols: + - dm + CMT: + name: Zentimeter + long_name: Zentimeter + symbols: + - cm + 4H: + name: Mikrometer + long_name: Mikrometer (Mikron) + symbols: + - µm + MMT: + name: Millimeter + long_name: Millimeter + symbols: + - mm + HMT: + name: Hektometer + long_name: Hektometer + symbols: + - hm + KMT: + name: Kilometer + long_name: Kilometer + symbols: + - km + A45: + name: Dekameter + long_name: Dekameter + symbols: + - dam + INH: + name: Zoll + long_name: Zoll + symbols: + - "″" + - in + FOT: + name: Fuß + long_name: Fuß + symbols: + - ft + YRD: + name: Yard + long_name: Yard + symbols: + - yd + SMI: + name: Meile + long_name: Meile (britisch) + symbols: + - mile + '77': + name: Millizoll + long_name: Millizoll + symbols: + - mil + MTK: + name: Quadratmeter + long_name: Quadratmeter + symbols: + - m² + - qm + DAA: + name: Dekar + long_name: Dekar + symbols: + - daa + KMK: + name: Quadratkilometer + long_name: Quadratkilometer + symbols: + - km² + CMK: + name: Quadratzentimeter + long_name: Quadratzentimeter + symbols: + - cm² + DMK: + name: Quadratdezimeter + long_name: Quadratdezimeter + symbols: + - dm² + H16: + name: Quadratdekameter + long_name: Quadratdekameter + symbols: + - dam² + H18: + name: Quadrathektometer + long_name: Quadrathektometer + symbols: + - hm² + MMK: + name: Quadratmillimeter + long_name: Quadratmillimeter + symbols: + - mm² + M47: + name: Circular mil + long_name: Circular mil + symbols: + - cmil + ARE: + name: Ar + long_name: Ar + symbols: + - a + HAR: + name: Hektar + long_name: Hektar + symbols: + - ha + INK: + name: Quadratzoll + long_name: Quadratzoll + symbols: + - in² + FTK: + name: Quadratfuß + long_name: Quadratfuß + symbols: + - ft² + YDK: + name: Quadratmeter + long_name: Quadratmeter + symbols: + - yd² + MIK: + name: Quadratmeile + long_name: Quadratmeile (britisch) + symbols: + - mi² + ACR: + name: Morgen + long_name: Morgen + symbols: + - acre + KGM: + name: Kilogramm + long_name: Kilogramm + symbols: + - kg + aliases: + - kilo + HGM: + name: Hektogramm + long_name: Hektogramm + symbols: + - hg + KTN: + name: Kilotonne + long_name: Kilotonne + symbols: + - kt + DJ: + name: Dekagramm + long_name: Dekagramm + symbols: + - dag + - dkg + DG: + name: Dezigramm + long_name: Dezigramm + symbols: + - dg + CGM: + name: Zentigramm + long_name: Zentigramm + symbols: + - cg + DTN: + name: Dezitonne + long_name: Dezitonne + symbols: + - dt + - dtn + MGM: + name: Milligramm + long_name: Milligramm + symbols: + - mg + 2U: + name: Megagramm + long_name: Megagramm + symbols: + - Mg + MC: + name: Mikrogramm + long_name: Mikrogramm + symbols: + - µg + GRM: + name: Gramm + long_name: Gramm + symbols: + - g + aliases: + - gr + - grm + TNE: + name: Tonne + long_name: Tonne (metrisch) + symbols: + - t + M86: + name: Pfund + long_name: Pfund + symbols: + - pfd + LBR: + name: Englisches Pfund + long_name: Englisches Pfund + symbols: + - lb + GRN: + name: Gran + long_name: Gran + symbols: + - gr + ONZ: + name: Unze + long_name: Unze (avoirdupois) + symbols: + - oz + CWI: + name: Br. Zentner + long_name: Hundredweight (britisch) + symbols: + - cwt + CWA: + name: Am. Zentner + long_name: Hundredweight (amerikanisch) + symbols: + - cwt + LTN: + name: Br. Tonne + long_name: Britische Tonne, Long Ton (amerikanisch) + symbols: + - ton (Br.) + STI: + name: Stein + long_name: Stein (britisch) + symbols: + - st + STN: + name: Am. Tonne + long_name: Amerikanische Tonne, Short Ton + symbols: + - ton (Am.) + APZ: + name: Feinunze + long_name: Feinunze oder Apothekerunze + symbols: + - tr oz + F13: + name: Slug + long_name: Slug + symbols: + - slug + SEC: + name: Sekunde + long_name: Sekunde [Zeiteinheit] + symbols: + - s + MIN: + name: Minute + long_name: Minute [Zeiteinheit] + symbols: + - min + HUR: + name: Stunde + long_name: Stunde + symbols: + - h + DAY: + name: Tag + long_name: Tag + symbols: + - d + C26: + name: Millisekunde + long_name: Millisekunde + symbols: + - ms + WEE: + name: Woche + long_name: Woche + symbols: + - wk + MON: + name: Monat + long_name: Monat + symbols: + - mo + ANN: + name: Jahr + long_name: Jahr + symbols: + - y + MTQ: + name: Kubikmeter + long_name: Kubikmeter + symbols: + - m³ + - cbm + LTR: + name: Liter + long_name: Liter + symbols: + - l + MAL: + name: Megaliter + long_name: Megaliter + symbols: + - Ml + DLT: + name: Deziliter + long_name: Deziliter + symbols: + - dl + 4G: + name: Mikroliter + long_name: Mikroliter + symbols: + - µl + K6: + name: Kiloliter + long_name: Kiloliter + symbols: + - kl + A44: + name: Dekaliter + long_name: Dekaliter + symbols: + - dal + MMQ: + name: Kubikmillimeter + long_name: Kubikmillimeter + symbols: + - mm³ + CMQ: + name: Kubikzentimeter + long_name: Kubikzentimeter + symbols: + - cm³ + - ccm + DMQ: + name: Kubikdezimeter + long_name: Kubikdezimeter + symbols: + - dm³ + MLT: + name: Milliliter + long_name: Milliliter + symbols: + - ml + HLT: + name: Hektoliter + long_name: Hektoliter + symbols: + - hl + CLT: + name: Zentiliter + long_name: Zentiliter + symbols: + - cl + DMA: + name: Kubikdekameter + long_name: Kubikdekameter + symbols: + - dam³ + H19: + name: Kubikhektometer + long_name: Kubikhektometer + symbols: + - hm³ + H20: + name: Kubikkilometer + long_name: Kubikkilometer + symbols: + - km³ + M67: + name: acre-foot + long_name: acre-foot (basierend auf U.S. Survey Foot) + symbols: + - acre-ft + M68: + name: Cord + long_name: Cord (128 ft3) + symbols: + - cord + M69: + name: Kubikmeile + long_name: Kubikmeile (britisch) + symbols: + - mi³ + M70: + name: Registertonne + long_name: Registertonne + symbols: + - RT + 5I: + name: Kubikfuß + long_name: Kubikfuß + symbols: + - std + INQ: + name: Kubikzoll + long_name: Kubikzoll + symbols: + - in³ + FTQ: + name: Kubikfuß + long_name: Kubikfuß + symbols: + - ft³ + YDQ: + name: Kubikmeter + long_name: Kubikmeter + symbols: + - yd³ + GLI: + name: Gallone (Br.) + long_name: Gallone (britisch) + symbols: + - gal + GLL: + name: Gallone (Am.) + long_name: Gallone (amerikanisch) + symbols: + - gal + PTI: + name: Pint (Br.) + long_name: Pint (britisch) + symbols: + - pt + QTI: + name: Quart + long_name: quart (britisch) + symbols: + - qt + PTL: + name: flüssiges Pint (Am.) + long_name: flüssiges Pint (amerikanisch) + symbols: + - liq pt + QTL: + name: flüssiges Quart (Am.) + long_name: flüssiges Quart (amerikanisch) + symbols: + - liq qt + PTD: + name: trockenes Pint (Am.) + long_name: trockenes Pint (amerikanisch) + symbols: + - dry pt + OZI: + name: flüssige Unze (Br.) + long_name: flüssige Unze (britisch) + symbols: + - fl oz + J57: + name: Barrel (Br.) + long_name: Barrel (britisch) + symbols: + - bbl + L43: + name: Viertelscheffel (Br.) + long_name: peck (britisch) + symbols: + - pk + L84: + name: Shipping ton (Br.) + long_name: ton (UK shipping) + symbols: + - Shipping ton (Br.) + L86: + name: Shipping ton (Am.) + long_name: ton (US shipping) + symbols: + - Shipping ton (Am.) + OZA: + name: Flüssigunze + long_name: Flüssigunze (amerikanisch) + symbols: + - fl oz + BUI: + name: Scheffel (Br.) + long_name: Scheffel (britisch) + symbols: + - bushel + BUA: + name: Scheffel (Am.) + long_name: Scheffel (amerikanisch) + symbols: + - bu + BLL: + name: Barrel (Am.) + long_name: Barrel (amerikanisch) + symbols: + - barrel + BLD: + name: Dry Barrel (Am.) + long_name: Dry Barrel (amerikanisch) + symbols: + - bbl + GLD: + name: Dry Gallon (Am.) + long_name: Dry Gallon (amerikanisch) + symbols: + - dry gal + QTD: + name: Dry Quart (Am.) + long_name: Dry Quart (amerikanisch) + symbols: + - dry qt + G26: + name: Ster + long_name: Ster (Schüttraummeter) + symbols: + - st + G21: + name: Tasse + long_name: Tasse [Volumeneinheit] + symbols: + - cup + G24: + name: Esslöffel + long_name: Esslöffel + symbols: + - EL + G25: + name: Teelöffel + long_name: Teelöffel + symbols: + - TL + G23: + name: Viertelscheffel (Am.) + long_name: Viertelscheffel (amerikanisch) + symbols: + - pk + JOU: + name: Joule + long_name: Joule + symbols: + - J + J75: + name: Kalorie + long_name: Kalorie + symbols: + - cal + K51: + name: Kilokalorie + long_name: Kilokalorie + symbols: + - kcal + J55: + name: Wattsekunde + long_name: Wattsekunde + symbols: + - Ws + WHR: + name: Wattstunde + long_name: Wattstunde + symbols: + - Wh + KWH: + name: Kilowattstunde + long_name: Kilowattstunde + symbols: + - kWh + GWH: + name: Gigawattstunde + long_name: Gigawattstunde + symbols: + - GWh + WTT: + name: Watt + long_name: Watt + symbols: + - W + KWT: + name: Kilowatt + long_name: Kilowatt + symbols: + - kW + MAW: + name: Megawatt + long_name: Megawatt + symbols: + - MW diff --git a/config/units-of-measure/locales/unece_en.yml b/config/units-of-measure/locales/unece_en.yml new file mode 100644 index 00000000..f6eb8730 --- /dev/null +++ b/config/units-of-measure/locales/unece_en.yml @@ -0,0 +1,761 @@ +# This file has been autogenerated by `rake units:build_i18n_from_csv` +--- +unece_units: + XCU: + name: Cup + XCN: + name: Container + XSH: + name: Sachet + X43: + name: Big bag + XST: + name: Sheet + XOK: + name: Block + XVA: + name: Vat + XBX: + name: Box + XBH: + name: Bunch + XBE: + name: Bundle + abbreviation: bdl. + XCX: + name: Can + XBJ: + name: Bucket + XUN: + name: Unit + XOS: + name: Oneway pallet + XDH: + name: Eurobox, CHEP box + abbreviation: box + XBA: + name: Barrel + XFI: + name: Firkin + XBO: + name: Bottle + abbreviation: btl. + aliases: + - btl + XBQ: + name: Protected bottle + abbreviation: btl. + XFB: + name: Flexibag + XFT: + name: Foodtainer + XJR: + name: Jar + XGR: + name: Glass receptacle + XOW: + name: Pallet sized large bag + X8B: + name: Wooden crate + XCV: + name: Cover + XWA: + name: Intermediate bulk container (IBC) + abbreviation: IBC + XEI: + name: Isothermic case + XJT: + name: Jutebag + XGY: + name: Gunny bag + XJY: + name: Jerrican + XBD: + name: Board + XCR: + name: Crate + XAI: + name: Clamshell + XPA: + name: Packet + XBK: + name: Basket + XBI: + name: Bin + XOV: + name: Returnable pallet + XNT: + name: Net + XPK: + name: Package + abbreviation: pkg. + aliases: + - pkg + XPC: + name: Parcel + XPX: + name: Pallet + X5M: + name: Paper bag + XPR: + name: Plastic receptacle + XEC: + name: Plastic bag + X6H: + name: Plastic composite packaging + X44: + name: Polybag + XBR: + name: Bar + XCW: + name: Roll cage + XBT: + name: Bolt + XSA: + name: Sack + XBM: + name: Bowl + XSX: + name: Set + XDN: + name: Dispenser + XAE: + name: Aerosol + XSC: + name: Shallow crate + XLU: + name: Lug + X5L: + name: Textile bag + XPP: + name: Piece + aliases: + - pc + XPU: + name: Tray + XBG: + name: Bag + XP2: + name: Pan + XCK: + name: Cask + XPT: + name: Pot + XGI: + name: Girder + XTU: + name: Tube + PTN: + name: portion + long_name: portion + symbols: [] + STC: + name: stick + long_name: stick + symbols: [] + DMT: + name: decimetre + long_name: decimetre + symbols: + - dm + CMT: + name: centimetre + long_name: centimetre + symbols: + - cm + 4H: + name: micrometre + long_name: micrometre (micron) + symbols: + - µm + MMT: + name: millimetre + long_name: millimetre + symbols: + - mm + HMT: + name: hectometre + long_name: hectometre + symbols: + - hm + KMT: + name: kilometre + long_name: kilometre + symbols: + - km + A45: + name: decametre + long_name: decametre + symbols: + - dam + INH: + name: inch + long_name: inch + symbols: + - in + FOT: + name: foot + long_name: foot + symbols: + - ft + YRD: + name: yard + long_name: yard + symbols: + - yd + SMI: + name: mile + long_name: mile (statute mile) + symbols: + - mile + '77': + name: milli-inch + long_name: milli-inch + symbols: + - mil + MTK: + name: square metre + long_name: square metre + symbols: + - m² + - sq m + DAA: + name: decare + long_name: decare + symbols: + - daa + KMK: + name: square kilometre + long_name: square kilometre + symbols: + - km² + - sq km + CMK: + name: square centimetre + long_name: square centimetre + symbols: + - cm² + - sq cm + DMK: + name: square decimetre + long_name: square decimetre + symbols: + - dm² + - sq dm + H16: + name: square decametre + long_name: square decametre + symbols: + - dam² + - sq dam + H18: + name: square hectometre + long_name: square hectometre + symbols: + - hm² + - sq hm + MMK: + name: square millimetre + long_name: square millimetre + symbols: + - mm² + - sq mm + M47: + name: circular mil + long_name: circular mil + symbols: + - cmil + ARE: + name: are + long_name: are + symbols: + - a + HAR: + name: hectare + long_name: hectare + symbols: + - ha + INK: + name: square inch + long_name: square inch + symbols: + - in² + - sq in + FTK: + name: square foot + long_name: square foot + symbols: + - ft² + - sq ft + YDK: + name: square yard + long_name: square yard + symbols: + - yd² + - sq yd + MIK: + name: square mile + long_name: square mile (statute mile) + symbols: + - mi² + - sq mi + ACR: + name: acre + long_name: acre + symbols: + - acre + KGM: + name: kilogram + long_name: kilogram + symbols: + - kg + aliases: + - kilo + HGM: + name: hectogram + long_name: hectogram + symbols: + - hg + KTN: + name: kilotonne + long_name: kilotonne + symbols: + - kt + DJ: + name: decagram + long_name: decagram + symbols: + - dag + - dkg + DG: + name: decigram + long_name: decigram + symbols: + - dg + CGM: + name: centigram + long_name: centigram + symbols: + - cg + DTN: + name: decitonne + long_name: decitonne + symbols: + - dt + - dtn + MGM: + name: milligram + long_name: milligram + symbols: + - mg + 2U: + name: megagram + long_name: megagram + symbols: + - Mg + MC: + name: microgram + long_name: microgram + symbols: + - µg + GRM: + name: gram + long_name: gram + symbols: + - g + aliases: + - gr + - grm + TNE: + name: metric ton + long_name: tonne (metric) + symbols: + - t + - mt + M86: + name: pfund + long_name: pfund + symbols: + - pfd + LBR: + name: pound + long_name: pound + symbols: + - lb + GRN: + name: grain + long_name: grain + symbols: + - gr + ONZ: + name: ounce + long_name: ounce (avoirdupois) + symbols: + - oz + CWI: + name: hundred weight + long_name: hundred weight (UK) + symbols: + - cwt + CWA: + name: hundred pound (cwt) / hundred weight (US) + long_name: hundred pound (cwt) / hundred weight (US) + symbols: + - cwt + LTN: + name: ton (UK) or long ton (US) + long_name: ton (UK) or long ton (US) + symbols: + - ton (UK) + STI: + name: stone + long_name: stone (UK) + symbols: + - st + STN: + name: ton (US) or short ton (UK/US) + long_name: ton (US) or short ton (UK/US) + symbols: + - ton (US) + APZ: + name: troy ounce + long_name: troy ounce or apothecary ounce + symbols: + - tr oz + F13: + name: slug + long_name: slug + symbols: + - slug + SEC: + name: second + long_name: second [unit of time] + symbols: + - s + MIN: + name: minute + long_name: minute [unit of time] + symbols: + - min + HUR: + name: hour + long_name: hour + symbols: + - h + DAY: + name: day + long_name: day + symbols: + - d + C26: + name: millisecond + long_name: millisecond + symbols: + - ms + WEE: + name: week + long_name: week + symbols: + - wk + MON: + name: month + long_name: month + symbols: + - mo + ANN: + name: year + long_name: year + symbols: + - y + MTQ: + name: cubic metre + long_name: cubic metre + symbols: + - m³ + LTR: + name: litre + long_name: litre + symbols: + - l + MAL: + name: megalitre + long_name: megalitre + symbols: + - Ml + DLT: + name: decilitre + long_name: decilitre + symbols: + - dl + 4G: + name: microlitre + long_name: microlitre + symbols: + - µl + K6: + name: kilolitre + long_name: kilolitre + symbols: + - kl + A44: + name: decalitre + long_name: decalitre + symbols: + - dal + MMQ: + name: cubic millimetre + long_name: cubic millimetre + symbols: + - mm³ + CMQ: + name: cubic centimetre + long_name: cubic centimetre + symbols: + - cm³ + - cc + - ccm + DMQ: + name: cubic decimetre + long_name: cubic decimetre + symbols: + - dm³ + MLT: + name: millilitre + long_name: millilitre + symbols: + - ml + HLT: + name: hectolitre + long_name: hectolitre + symbols: + - hl + CLT: + name: centilitre + long_name: centilitre + symbols: + - cl + DMA: + name: cubic decametre + long_name: cubic decametre + symbols: + - dam³ + H19: + name: cubic hectometre + long_name: cubic hectometre + symbols: + - hm³ + H20: + name: cubic kilometre + long_name: cubic kilometre + symbols: + - km³ + M67: + name: acre-foot + long_name: acre-foot (based on U.S. survey foot) + symbols: + - acre-ft + M68: + name: cord + long_name: cord (128 ft3) + symbols: + - cord + M69: + name: cubic mile + long_name: cubic mile (UK statute) + symbols: + - mi³ + M70: + name: register ton + long_name: register ton + symbols: + - RT + 5I: + name: standard cubic foot + long_name: standard cubic foot + symbols: + - std + INQ: + name: cubic inch + long_name: cubic inch + symbols: + - in³ + FTQ: + name: cubic foot + long_name: cubic foot + symbols: + - ft³ + YDQ: + name: cubic yard + long_name: cubic yard + symbols: + - yd³ + GLI: + name: gallon (UK) + long_name: gallon (UK) + symbols: + - gal + GLL: + name: gallon (US) + long_name: gallon (US) + symbols: + - gal + PTI: + name: pint (UK) + long_name: pint (UK) + symbols: + - pt + QTI: + name: quart (UK) + long_name: quart (UK) + symbols: + - qt + PTL: + name: liquid pint (US) + long_name: liquid pint (US) + symbols: + - liq pt + QTL: + name: liquid quart (US) + long_name: liquid quart (US) + symbols: + - liq qt + PTD: + name: dry pint (US) + long_name: dry pint (US) + symbols: + - dry pt + OZI: + name: fluid ounce (UK) + long_name: fluid ounce (UK) + symbols: + - fl oz + J57: + name: barrel (UK petroleum) + long_name: barrel (UK petroleum) + symbols: + - bbl + L43: + name: peck (UK) + long_name: peck (UK) + symbols: + - pk (UK) + L84: + name: ton (UK shipping) + long_name: ton (UK shipping) + symbols: + - British shipping ton + L86: + name: ton (US shipping) + long_name: ton (US shipping) + symbols: + - "(US) shipping ton" + OZA: + name: fluid ounce (US) + long_name: fluid ounce (US) + symbols: + - fl oz + BUI: + name: bushel (UK) + long_name: bushel (UK) + symbols: + - bushel + BUA: + name: bushel (US) + long_name: bushel (US) + symbols: + - bu + BLL: + name: barrel (US) + long_name: barrel (US) + symbols: + - barrel + BLD: + name: dry barrel (US) + long_name: dry barrel (US) + symbols: + - bbl + GLD: + name: dry gallon (US) + long_name: dry gallon (US) + symbols: + - dry gal + QTD: + name: dry quart (US) + long_name: dry quart (US) + symbols: + - dry qt + G26: + name: stere + long_name: stere + symbols: + - st + G21: + name: cup + long_name: cup [unit of volume] + symbols: + - cup + G24: + name: tablespoon + long_name: tablespoon (US) + symbols: + - tbsp. + G25: + name: teaspoon + long_name: teaspoon (US) + symbols: + - tsp. + G23: + name: peck (US) + long_name: peck (US) + symbols: + - pk (US) + JOU: + name: joule + long_name: joule + symbols: + - J + J75: + name: calorie + long_name: calorie (mean) + symbols: + - cal + K51: + name: kilocalorie + long_name: kilocalorie (mean) + symbols: + - kcal + J55: + name: watt second + long_name: watt second + symbols: + - Ws + WHR: + name: watt hour + long_name: watt hour + symbols: + - Wh + KWH: + name: kilowatt hour + long_name: kilowatt hour + symbols: + - kWh + GWH: + name: gigawatt hour + long_name: gigawatt hour + symbols: + - GWh + WTT: + name: watt + long_name: watt + symbols: + - W + KWT: + name: kilowatt + long_name: kilowatt + symbols: + - kW + MAW: + name: megawatt + long_name: megawatt + symbols: + - MW diff --git a/config/units-of-measure/translations_piece.csv b/config/units-of-measure/translations_piece.csv new file mode 100644 index 00000000..d4df81de --- /dev/null +++ b/config/units-of-measure/translations_piece.csv @@ -0,0 +1,69 @@ +Common code,Original name,English suggestion,Engl. abbr.,Engl. Aliases,German suggestion,German abbr.,German aliases,Description +XCU,Cup,Cup,,,Becher,,, +XCN,"Container, not otherwise specified as transport equipment",Container,,,Behälter,,, +XSH,Sachet ,Sachet ,,,Beutel,,, +X43,"Bag, super bulk",Big bag,,,Bigbag,,,A cloth plastic or paper based bag having the dimensions of the pallet on which it is constructed. +XST,Sheet,Sheet,,,Blatt,Bl.,, +XOK,Block,Block,,,Block,,,"A solid piece of a hard substance, such as granite, having one or more flat sides." +XVA,Vat,Vat,,,Bottich,,, +XBX,Box,Box,,,Box,,, +XBH,Bunch,Bunch,,,Bund,Bd.,bd, +XBE,Bundle ,Bundle ,bdl.,,Bündel ,Bdl.,, +XCX,"Can, cylindrical ",Can,,,Dose,,, +XBJ,Bucket ,Bucket ,,,Eimer ,,, +XUN,Unit,Unit,,,Einheit,,,"A type of package composed of a single item or object, not otherwise specified as a unit of transport equipment." +XOS,Oneway pallet,Oneway pallet,,,Einweg-Palette,,,Pallet need not be returned to the point of expedition +XDH,"Box, Commonwealth Handling Equipment Pool (CHEP), Eurobox","Eurobox, CHEP box",box,,"Eurobox, CHEP-Box",Box,,A box mounted on a pallet base under the control of CHEP. +XBA,Barrel ,Barrel ,,,Fass,,, +XFI,Firkin ,Firkin ,,,Fässchen ,,, +XBO,"Bottle, non-protected, cylindrical ",Bottle,btl.,btl,Flasche,Fl.,fl,A narrow-necked cylindrical shaped vessel without external protective packing material. +XBQ,"Bottle, protected cylindrical",Protected bottle,btl.,,Flasche in Umverpackung,Fl.,,A narrow-necked cylindrical shaped vessel with external protective packing material. +XFB,Flexibag,Flexibag,,,Flexibag,,,"A flexible containment bag made of plastic, typically for the transportation bulk non-hazardous cargoes using standard size shipping containers." +XFT,Foodtainer,Foodtainer,,,Foodtainer,,, +XJR,Jar,Jar,,,Glas,Gl.,gl, +XGR,"Receptacle, glass ",Glass receptacle,,,Glasgefäß,,,Containment vessel made of glass for retaining substances or articles. +XOW,"Large bag, pallet sized",Pallet sized large bag,,,Großer Sack in Palettengröße,,,"A non-rigid container made of fabric, paper, plastic, etc, with an opening at the top which can be closed and which is suitable for use on pallets" +X8B,"Crate, wooden",Wooden crate,,,Holzkiste,,,"A receptacle, made of wood, on which goods are retained for ease of mechanical handling during transport and storage." +XCV,Cover,Cover,,,Hülle,,, +XWA,Intermediate bulk container,Intermediate bulk container (IBC),IBC,,Intermediate Bulk Container (IBC),IBC,,"A reusable container made of metal, plastic, textile, wood or composite materials used to facilitate transportation of bulk solids and liquids in manageable volumes." +XEI,"Case, isothermic ",Isothermic case,,,Isotherme Box,,, +XJT,Jutebag,Jutebag,,,Jutebeutel,,, +XGY,"Bag, gunny",Gunny bag,,,Jutesack,,,"A sack made of gunny or burlap, used for transporting coarse commodities, such as grains, potatoes, and other agricultural products. " +XJY,"Jerrican, cylindrical",Jerrican,,,Kanister,,, +XBD,Board,Board,,,Karton,,, +XCR,Crate,Crate,,,Kiste,,, +XAI,Clamshell,Clamshell,,,Klappverpackung,,, +XPA,Packet ,Packet ,,,Kollo,,,Small package. +XBK,Basket ,Basket ,,,Korb ,,, +XBI,Bin,Bin,,,Kübel,,, +XOV,Returnable pallet,Returnable pallet,,,Mehrwegpalette,,,Pallet must be returned to the point of expedition. +XNT,Net,Net,,,Netz,,, +XPK,Package,Package,pkg.,pkg,Packung,Pkg.,"pkg, pkt",Standard packaging unit. +XPC,Parcel ,Parcel ,,,Paket ,,, +XPX,Pallet ,Pallet ,,,Palette ,,,"Platform or open-ended box, usually made of wood, on which goods are retained for ease of mechanical handling during transport and storage." +X5M,"Bag, paper ",Paper bag,,,Papiertüte,,, +XPR,"Receptacle, plastic ",Plastic receptacle,,,Plastikgefäß,,,Containment vessel made of plastic for retaining substances or articles. +XEC,"Bag, plastic ",Plastic bag,,,Plastiktüte,,, +X6H,"Composite packaging, plastic receptacle",Plastic composite packaging,,,Plastik-Verbundverpackung,,, +X44,"Bag, polybag",Polybag,,,Polybeutel,,,"A type of plastic bag, typically used to wrap promotional pieces, publications, product samples, and/or catalogues. " +XBR,Bar,Bar,,,Riegel,,, +XCW,"Cage, roll ",Roll cage,,,Rollcontainer,,, +XBT,Bolt ,Bolt ,,,Rolle,,, +XSA,Sack ,Sack ,,,Sack ,,, +XBM,Basin,Bowl,,,Schüssel,,, +XSX,Set,Set,,,Set,,, +XDN,Dispenser,Dispenser,,,Spender,,, +XAE,Aerosol,Aerosol,,,Sprühdose,,, +XSC,"Crate, shallow ",Shallow crate,,,Steige,,, +XLU,Lug,Lug,,,Stiege,,,A wooden box for the transportation and storage of fruit or vegetables. +X5L,"Bag, textile ",Textile bag,,,Stofftüte,,, +XPP,Piece,Piece,,pc,Stück,,"stk, st, stueck",A loose or unpacked article. +XPU,Tray ,Tray ,,,Schale,,, +XBG,Bag,Bag,,,Tasche,,,A receptacle made of flexible material with an open or closed top. +XP2,Pan,Pan,,,Tiegel,,,"A shallow, wide, open container, usually of metal." +XCK,Cask ,Cask ,,,Tonne,,, +XPT,Pot,Pot,,,Topf,,tiegel, +XGI,Girder ,Girder ,,,Träger ,,, +XTU,Tube ,Tube ,,,Tube,,, +PTN,Portion,Portion,,,Portion,,port, +STC,Sick,Stick,,,Stange,,, diff --git a/config/units-of-measure/translations_scalar-de.csv b/config/units-of-measure/translations_scalar-de.csv new file mode 100644 index 00000000..37730e91 --- /dev/null +++ b/config/units-of-measure/translations_scalar-de.csv @@ -0,0 +1,120 @@ +common code,full name,short name,symbol,alt. Symbols,aliases,description +DMT,Dezimeter,Dezimeter,dm,,, +CMT,Zentimeter,Zentimeter,cm,,, +4H,Mikrometer (Mikron),Mikrometer,µm,,, +MMT,Millimeter,Millimeter,mm,,, +HMT,Hektometer,Hektometer,hm,,, +KMT,Kilometer,Kilometer,km,,, +A45,Dekameter,Dekameter,dam,,, +INH,Zoll,Zoll,″,in,, +FOT,Fuß,Fuß,ft,,, +YRD,Yard,Yard,yd,,, +SMI,Meile (britisch),Meile,mile,,, +77,Millizoll,Millizoll,mil,,, +MTK,Quadratmeter,Quadratmeter,m²,qm,, +DAA,Dekar,Dekar,daa,,, +KMK,Quadratkilometer,Quadratkilometer,km²,,, +CMK,Quadratzentimeter,Quadratzentimeter,cm²,,, +DMK,Quadratdezimeter,Quadratdezimeter,dm²,,, +H16,Quadratdekameter,Quadratdekameter,dam²,,,Synonym: Ar +H18,Quadrathektometer,Quadrathektometer,hm²,,,Synonym: Hektar +MMK,Quadratmillimeter,Quadratmillimeter,mm²,,, +M47,Circular mil ,Circular mil ,cmil,,,"Unit of an area, of which the size is given by a diameter of length of 1 mm (0,001 in) based on the formula: area = p·(diameter/2)²." +ARE,Ar,Ar,a,,,Synonym: Quadratdekameter +HAR,Hektar,Hektar,ha,,,Synonym: Quadrathektometer +INK,Quadratzoll,Quadratzoll,in²,,, +FTK,Quadratfuß,Quadratfuß,ft²,,, +YDK,Quadratmeter,Quadratmeter,yd²,,, +MIK,Quadratmeile (britisch),Quadratmeile,mi²,,, +ACR,Morgen,Morgen,acre,,, +KGM,Kilogramm,Kilogramm,kg,,kilo, +HGM,Hektogramm,Hektogramm,hg,,, +KTN,Kilotonne,Kilotonne,kt,,, +DJ,Dekagramm,Dekagramm,dag,dkg,, +DG,Dezigramm,Dezigramm,dg,,, +CGM,Zentigramm,Zentigramm,cg,,, +DTN,Dezitonne,Dezitonne,dt,dtn,,"Synonym: Zentner (A, CH)" +MGM,Milligramm,Milligramm,mg,,, +2U,Megagramm,Megagramm,Mg,,, +MC,Mikrogramm,Mikrogramm,µg,,, +GRM,Gramm,Gramm,g,,"gr, grm", +TNE,Tonne (metrisch),Tonne,t,,, +M86,Pfund,Pfund,pfd,,,Veraltete deutsche Masseeinheit +LBR,Englisches Pfund,Englisches Pfund,lb,,, +GRN,Gran,Gran,gr,,, +ONZ,Unze (avoirdupois),Unze,oz,,, +CWI,Hundredweight (britisch),Br. Zentner,cwt,,, +CWA,Hundredweight (amerikanisch),Am. Zentner,cwt,,, +LTN,"Britische Tonne, Long Ton (amerikanisch)",Br. Tonne,ton (Br.),,,Synonym: gross ton (2240 lb) +STI,Stein (britisch),Stein,st,,, +STN,"Amerikanische Tonne, Short Ton",Am. Tonne,ton (Am.),,,Synonym: net ton (2000 lb) +APZ,Feinunze oder Apothekerunze,Feinunze,tr oz,,, +F13,Slug,Slug,slug,,,A unit of mass. One slug is the mass accelerated at 1 foot per second per second by a force of 1 pound. +SEC,Sekunde [Zeiteinheit],Sekunde,s,,, +MIN,Minute [Zeiteinheit],Minute,min,,, +HUR,Stunde,Stunde,h,,, +DAY,Tag,Tag,d,,, +C26,Millisekunde,Millisekunde,ms,,, +WEE,Woche,Woche,wk,,, +MON,Monat,Monat,mo,,,"Zeiteinheit gleich 1/12 eines Jahres oder 365,25 Tage." +ANN,Jahr,Jahr,y,,,"Zeiteinheit gleich 365,25 Tage (Julianisches Jahr)" +MTQ,Kubikmeter,Kubikmeter,m³,cbm,, +LTR,Liter,Liter,l,,, +MAL,Megaliter,Megaliter,Ml,,, +DLT,Deziliter,Deziliter,dl,,, +4G,Mikroliter,Mikroliter,µl,,, +K6,Kiloliter,Kiloliter,kl,,, +A44,Dekaliter,Dekaliter,dal,,, +MMQ,Kubikmillimeter,Kubikmillimeter,mm³,,, +CMQ,Kubikzentimeter,Kubikzentimeter,cm³,ccm,, +DMQ,Kubikdezimeter,Kubikdezimeter,dm³,,, +MLT,Milliliter,Milliliter,ml,,, +HLT,Hektoliter,Hektoliter,hl,,, +CLT,Zentiliter,Zentiliter,cl,,, +DMA,Kubikdekameter,Kubikdekameter,dam³,,, +H19,Kubikhektometer,Kubikhektometer,hm³,,, +H20,Kubikkilometer,Kubikkilometer,km³,,, +M67,acre-foot (basierend auf U.S. Survey Foot),acre-foot,acre-ft,,,"Unit of the volume, which is Am.ed in the United States to measure/gauge the capacity of reservoirs." +M68,Cord (128 ft3),Cord,cord,,,Traditional unit of the volume of stacked firewood which has been measured with a cord. +M69,Kubikmeile (britisch),Kubikmeile,mi³,,,Unit of volume according to the Imperial system of units. +M70,Registertonne,Registertonne,RT,,,Traditional unit of the cargo capacity. +5I,Kubikfuß,Kubikfuß,std,,, +INQ,Kubikzoll,Kubikzoll,in³,,, +FTQ,Kubikfuß,Kubikfuß,ft³,,, +YDQ,Kubikmeter,Kubikmeter,yd³,,, +GLI,Gallone (britisch),Gallone (Br.),gal,,, +GLL,Gallone (amerikanisch),Gallone (Am.),gal,,, +PTI,Pint (britisch),Pint (Br.),pt,,, +QTI,quart (britisch),Quart,qt,,, +PTL,flüssiges Pint (amerikanisch),flüssiges Pint (Am.),liq pt,,, +QTL,flüssiges Quart (amerikanisch),flüssiges Quart (Am.),liq qt,,, +PTD,trockenes Pint (amerikanisch),trockenes Pint (Am.),dry pt,,, +OZI,flüssige Unze (britisch),flüssige Unze (Br.),fl oz,,, +J57,Barrel (britisch),Barrel (Br.),bbl,,, +L43,peck (britisch),Viertelscheffel (Br.),pk,,, +L84,ton (UK shipping),Shipping ton (Br.),Shipping ton (Br.),,, +L86,ton (US shipping),Shipping ton (Am.),Shipping ton (Am.),,, +OZA,Flüssigunze (amerikanisch),Flüssigunze,fl oz,,, +BUI,Scheffel (britisch),Scheffel (Br.),bushel,,, +BUA,Scheffel (amerikanisch),Scheffel (Am.),bu,,, +BLL,Barrel (amerikanisch),Barrel (Am.),barrel,,, +BLD,Dry Barrel (amerikanisch),Dry Barrel (Am.),bbl,,, +GLD,Dry Gallon (amerikanisch),Dry Gallon (Am.),dry gal,,, +QTD,Dry Quart (amerikanisch),Dry Quart (Am.),dry qt,,, +G26,Ster (Schüttraummeter),Ster,st,,,Kubikmeter +G21,Tasse [Volumeneinheit],Tasse,cup,,, +G24,Esslöffel,Esslöffel,EL,,, +G25,Teelöffel,Teelöffel,TL,,, +G23,Viertelscheffel (amerikanisch),Viertelscheffel (Am.),pk,,, +JOU,Joule,Joule,J,,, +J75,Kalorie,Kalorie,cal,,, +K51,Kilokalorie,Kilokalorie,kcal,,, +J55,Wattsekunde,Wattsekunde,Ws,,, +WHR,Wattstunde,Wattstunde,Wh,,, +KWH,Kilowattstunde,Kilowattstunde,kWh,,, +GWH,Gigawattstunde,Gigawattstunde,GWh,,, +WTT,Watt,Watt,W,,, +KWT,Kilowatt,Kilowatt,kW,,, +MAW,Megawatt,Megawatt,MW,,,A unit of power defining the rate of energy transferred or consumed when a current of 1000 amperes flows due to a potential of 1000 volts at unity power factor. +PTN,Portion,Portion,,,, +STC,Stange,Stange,,,, diff --git a/config/units-of-measure/translations_scalar-en.csv b/config/units-of-measure/translations_scalar-en.csv new file mode 100644 index 00000000..3c4baae8 --- /dev/null +++ b/config/units-of-measure/translations_scalar-en.csv @@ -0,0 +1,121 @@ +common code,full name,short name,symbol,alt. Symbols,aliases,description +DMT,decimetre,decimetre,dm,,, +CMT,centimetre,centimetre,cm,,, +4H,micrometre (micron),micrometre,µm,,, +MMT,millimetre,millimetre,mm,,, +HMT,hectometre,hectometre,hm,,, +KMT,kilometre,kilometre,km,,, +A45,decametre,decametre,dam,,, +INH,inch,inch,in,,, +FOT,foot,foot,ft,,, +YRD,yard,yard,yd,,, +SMI,mile (statute mile),mile,mile,,, +77,milli-inch,milli-inch,mil,,, +MTK,square metre,square metre,m²,sq m,, +DAA,decare,decare,daa,,, +KMK,square kilometre,square kilometre,km²,sq km,, +CMK,square centimetre,square centimetre,cm²,sq cm,, +DMK,square decimetre,square decimetre,dm²,sq dm,, +H16,square decametre,square decametre,dam²,sq dam,,Synonym: are +H18,square hectometre,square hectometre,hm²,sq hm,,Synonym: hectare +MMK,square millimetre,square millimetre,mm²,sq mm,, +M47,circular mil ,circular mil ,cmil,,,"Unit of an area, of which the size is given by a diameter of length of 1 mm (0,001 in) based on the formula: area = p·(diameter/2)²." +ARE,are,are,a,,,Synonym: square decametre +HAR,hectare,hectare,ha,,,Synonym: square hectometre +INK,square inch,square inch,in²,sq in,, +FTK,square foot,square foot,ft²,sq ft,, +YDK,square yard,square yard,yd²,sq yd,, +MIK,square mile (statute mile),square mile,mi²,sq mi,, +ACR,acre,acre,acre,,, +KGM,kilogram,kilogram,kg,,kilo,A unit of mass equal to one thousand grams. +HGM,hectogram,hectogram,hg,,, +KTN,kilotonne,kilotonne,kt,,, +DJ,decagram,decagram,dag,dkg,, +DG,decigram,decigram,dg,,, +CGM,centigram,centigram,cg,,, +DTN,decitonne,decitonne,dt,dtn,,"Synonym: centner, metric 100 kg; quintal, metric 100 kg" +MGM,milligram,milligram,mg,,, +2U,megagram,megagram,Mg,,, +MC,microgram,microgram,µg,,, +GRM,gram,gram,g,,"gr, grm", +TNE,tonne (metric),metric ton,t,mt,, +M86,pfund,pfund,pfd,,,Outdated unit of the mass used in Germany. +LBR,pound,pound,lb,,, +GRN,grain,grain,gr,,, +ONZ,ounce (avoirdupois),ounce,oz,,, +CWI,hundred weight (UK),hundred weight,cwt,,, +CWA,hundred pound (cwt) / hundred weight (US),hundred pound (cwt) / hundred weight (US),cwt,,, +LTN,ton (UK) or long ton (US),ton (UK) or long ton (US),ton (UK),,,Synonym: gross ton (2240 lb) +STI,stone (UK),stone,st,,, +STN,ton (US) or short ton (UK/US),ton (US) or short ton (UK/US),ton (US),,,Synonym: net ton (2000 lb) +APZ,troy ounce or apothecary ounce,troy ounce,tr oz,,, +F13,slug,slug,slug,,,A unit of mass. One slug is the mass accelerated at 1 foot per second per second by a force of 1 pound. +SEC,second [unit of time],second,s,,, +MIN,minute [unit of time],minute,min,,, +HUR,hour,hour,h,,, +DAY,day,day,d,,, +C26,millisecond,millisecond,ms,,, +WEE,week,week,wk,,, +MON,month,month,mo,,,"Unit of time equal to 1/12 of a year of 365,25 days." +ANN,year,year,y,,,"Unit of time equal to 365,25 days. +Synonym: Julian year" +MTQ,cubic metre,cubic metre,m³,,,Synonym: metre cubed +LTR,litre,litre,l,,, +MAL,megalitre,megalitre,Ml,,, +DLT,decilitre,decilitre,dl,,, +4G,microlitre,microlitre,µl,,, +K6,kilolitre,kilolitre,kl,,, +A44,decalitre,decalitre,dal,,, +MMQ,cubic millimetre,cubic millimetre,mm³,,, +CMQ,cubic centimetre,cubic centimetre,cm³,"cc, ccm",, +DMQ,cubic decimetre,cubic decimetre,dm³,,, +MLT,millilitre,millilitre,ml,,, +HLT,hectolitre,hectolitre,hl,,, +CLT,centilitre,centilitre,cl,,, +DMA,cubic decametre,cubic decametre,dam³,,, +H19,cubic hectometre,cubic hectometre,hm³,,, +H20,cubic kilometre,cubic kilometre,km³,,, +M67,acre-foot (based on U.S. survey foot),acre-foot,acre-ft,,,"Unit of the volume, which is used in the United States to measure/gauge the capacity of reservoirs." +M68,cord (128 ft3),cord,cord,,,Traditional unit of the volume of stacked firewood which has been measured with a cord. +M69,cubic mile (UK statute),cubic mile,mi³,,,Unit of volume according to the Imperial system of units. +M70,register ton,register ton,RT,,,Traditional unit of the cargo capacity. +5I,standard cubic foot,standard cubic foot,std,,,Use standard (common code WSD) +INQ,cubic inch,cubic inch,in³,,,Synonym: inch cubed +FTQ,cubic foot,cubic foot,ft³,,, +YDQ,cubic yard,cubic yard,yd³,,, +GLI,gallon (UK),gallon (UK),gal,,, +GLL,gallon (US),gallon (US),gal,,, +PTI,pint (UK),pint (UK),pt,,, +QTI,quart (UK),quart (UK),qt,,, +PTL,liquid pint (US),liquid pint (US),liq pt,,, +QTL,liquid quart (US),liquid quart (US),liq qt,,, +PTD,dry pint (US),dry pint (US),dry pt,,, +OZI,fluid ounce (UK),fluid ounce (UK),fl oz,,, +J57,barrel (UK petroleum),barrel (UK petroleum),bbl,,, +L43,peck (UK),peck (UK),pk (UK),,, +L84,ton (UK shipping),ton (UK shipping),British shipping ton,,, +L86,ton (US shipping),ton (US shipping),(US) shipping ton,,, +OZA,fluid ounce (US),fluid ounce (US),fl oz,,, +BUI,bushel (UK),bushel (UK),bushel,,, +BUA,bushel (US),bushel (US),bu,,, +BLL,barrel (US),barrel (US),barrel,,, +BLD,dry barrel (US),dry barrel (US),bbl,,, +GLD,dry gallon (US),dry gallon (US),dry gal,,, +QTD,dry quart (US),dry quart (US),dry qt,,, +G26,stere,stere,st,,,cubic metre +G21,cup [unit of volume],cup,cup,,, +G24,tablespoon (US),tablespoon,tbsp.,,, +G25,teaspoon (US),teaspoon,tsp.,,, +G23,peck (US),peck (US),pk (US),,, +JOU,joule,joule,J,,, +J75,calorie (mean),calorie,cal,,, +K51,kilocalorie (mean),kilocalorie,kcal,,, +J55,watt second,watt second,Ws,,, +WHR,watt hour,watt hour,Wh,,, +KWH,kilowatt hour,kilowatt hour,kWh,,, +GWH,gigawatt hour,gigawatt hour,GWh,,, +WTT,watt,watt,W,,, +KWT,kilowatt,kilowatt,kW,,, +MAW,megawatt,megawatt,MW,,,A unit of power defining the rate of energy transferred or consumed when a current of 1000 amperes flows due to a potential of 1000 volts at unity power factor. +PTN,portion,portion,,,, +STC,stick,stick,,,, diff --git a/config/units-of-measure/un-ece-20-remastered.yml b/config/units-of-measure/un-ece-20-remastered.yml new file mode 100644 index 00000000..d6e484d0 --- /dev/null +++ b/config/units-of-measure/un-ece-20-remastered.yml @@ -0,0 +1,22825 @@ +# This file has been autogenerated by `rake units:clean_unece20_source` +--- +- Status: X + LevelAndCategory: '3.9' + Name: lift + ConversionFactor: '' + Symbol: '' + CommonCode: '05' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: small spray + ConversionFactor: '' + Symbol: '' + CommonCode: '06' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: heat lot + ConversionFactor: '' + Symbol: '' + CommonCode: '08' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: group + ConversionFactor: '' + Symbol: '' + CommonCode: '10' + Description: 'A unit of count defining the number of groups (group: set of items + classified together).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: outfit + ConversionFactor: '' + Symbol: '' + CommonCode: '11' + Description: 'A unit of count defining the number of outfits (outfit: a complete + set of equipment / materials / objects used for a specific purpose).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: ration + ConversionFactor: '' + Symbol: '' + CommonCode: '13' + Description: 'A unit of count defining the number of rations (ration: a single portion + of provisions).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: shot + ConversionFactor: '' + Symbol: '' + CommonCode: '14' + Description: A unit of liquid measure, especially related to spirits. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: stick, military + ConversionFactor: '' + Symbol: '' + CommonCode: '15' + Description: 'A unit of count defining the number of military sticks (military stick: + bombs or paratroops released in rapid succession from an aircraft).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: hundred fifteen kg drum + ConversionFactor: '' + Symbol: '' + CommonCode: '16' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: hundred lb drum + ConversionFactor: '' + Symbol: '' + CommonCode: '17' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: fiftyfive gallon (US) drum + ConversionFactor: '' + Symbol: '' + CommonCode: '18' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: tank truck + ConversionFactor: '' + Symbol: '' + CommonCode: '19' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: twenty foot container + ConversionFactor: '' + Symbol: '' + CommonCode: '20' + Description: A unit of count defining the number of shipping containers that measure + 20 foot in length. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: forty foot container + ConversionFactor: '' + Symbol: '' + CommonCode: '21' + Description: A unit of count defining the number of shipping containers that measure + 40 foot in length. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: decilitre per gram + ConversionFactor: 10⁻¹ x m³/kg + Symbol: dl/g + CommonCode: '22' + Description: '' + conversion: + factor: 0.1 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: gram per cubic centimetre + ConversionFactor: 10³ kg/m³ + Symbol: g/cm³ + CommonCode: '23' + Description: '' + conversion: + factor: 1000.0 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '3.1' + Name: theoretical pound + ConversionFactor: '' + Symbol: '' + CommonCode: '24' + Description: A unit of mass defining the expected mass of material expressed as + the number of pounds. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: gram per square centimetre + ConversionFactor: 10 kg/m² + Symbol: g/cm² + CommonCode: '25' + Description: '' + conversion: + factor: 10.0 + base_units: + - '28' +- Status: X + LevelAndCategory: '3.1' + Name: actual ton + ConversionFactor: '' + Symbol: '' + CommonCode: '26' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: theoretical ton + ConversionFactor: '' + Symbol: '' + CommonCode: '27' + Description: A unit of mass defining the expected mass of material, expressed as + the number of tons. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: kilogram per square metre + ConversionFactor: kg/m² + Symbol: kg/m² + CommonCode: '28' + Description: '' + conversion: + factor: 1.0 + base_units: + - '28' +- Status: X + LevelAndCategory: '3.8' + Name: pound per thousand square foot + ConversionFactor: '' + Symbol: lb/kft² + CommonCode: '29' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: horse power day per air dry metric ton + ConversionFactor: '' + Symbol: '' + CommonCode: '30' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: catch weight + ConversionFactor: '' + Symbol: '' + CommonCode: '31' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: kilogram per air dry metric ton + ConversionFactor: '' + Symbol: '' + CommonCode: '32' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: kilopascal square metre per gram + ConversionFactor: 10⁶ m/s² + Symbol: kPa·m²/g + CommonCode: '33' + Description: '' + conversion: + factor: 1000000.0 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: 1M + Name: kilopascal per millimetre + ConversionFactor: 10⁶ kg/(m² x s²) + Symbol: kPa/mm + CommonCode: '34' + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per square centimetre second + ConversionFactor: 10⁻² m/s + Symbol: ml/(cm²·s) + CommonCode: '35' + Description: '' + conversion: + factor: 0.01 + base_units: + - MTS + - P87 +- Status: X + LevelAndCategory: 1M + Name: cubic foot per minute per square foot + ConversionFactor: '' + Symbol: ft³/(min/ft²) + CommonCode: '36' + Description: Conversion factor required + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: ounce per square foot + ConversionFactor: 0,305 151 7 kg/m² + Symbol: oz/ft² + CommonCode: '37' + Description: '' + conversion: + factor: 0.3051517 + base_units: + - '28' +- Status: '' + LevelAndCategory: '3.9' + Name: ounce per square foot per 0,01inch + ConversionFactor: '' + Symbol: oz/(ft²/cin) + CommonCode: '38' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: millilitre per second + ConversionFactor: 10⁻⁶ m³/s + Symbol: ml/s + CommonCode: '40' + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1M + Name: millilitre per minute + ConversionFactor: 1,666 67 x 10⁻⁸ m³/s + Symbol: ml/min + CommonCode: '41' + Description: '' + conversion: + factor: 1.66667e-08 + base_units: + - MQS +- Status: X + LevelAndCategory: '3.3' + Name: super bulk bag + ConversionFactor: '' + Symbol: '' + CommonCode: '43' + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: fivehundred kg bulk bag + ConversionFactor: '' + Symbol: '' + CommonCode: '44' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: threehundred kg bulk bag + ConversionFactor: '' + Symbol: '' + CommonCode: '45' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: fifty lb bulk bag + ConversionFactor: '' + Symbol: '' + CommonCode: '46' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: fifty lb bag + ConversionFactor: '' + Symbol: '' + CommonCode: '47' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: bulk car load + ConversionFactor: '' + Symbol: '' + CommonCode: '48' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: theoretical kilogram + ConversionFactor: '' + Symbol: '' + CommonCode: '53' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: theoretical tonne + ConversionFactor: '' + Symbol: '' + CommonCode: '54' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: sitas + ConversionFactor: '' + Symbol: '' + CommonCode: '56' + Description: A unit of area for tin plate equal to a surface area of 100 square + metres. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: mesh + ConversionFactor: '' + Symbol: '' + CommonCode: '57' + Description: A unit of count defining the number of strands per inch as a measure + of the fineness of a woven product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: net kilogram + ConversionFactor: '' + Symbol: '' + CommonCode: '58' + Description: A unit of mass defining the total number of kilograms after deductions. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: part per million + ConversionFactor: 1 x 10⁻⁶ + Symbol: ppm + CommonCode: '59' + Description: A unit of proportion equal to 10⁻⁶. + conversion: + factor: 1.0e-06 +- Status: '' + LevelAndCategory: '3.7' + Name: percent weight + ConversionFactor: 1 x 10⁻² + Symbol: '' + CommonCode: '60' + Description: A unit of proportion equal to 10⁻². + conversion: + factor: 0.01 +- Status: '' + LevelAndCategory: '3.7' + Name: part per billion (US) + ConversionFactor: 1 x 10⁻⁹ + Symbol: ppb + CommonCode: '61' + Description: A unit of proportion equal to 10⁻⁹. + conversion: + factor: 1.0e-09 +- Status: X + LevelAndCategory: '3.7' + Name: percent per 1000 hour + ConversionFactor: '' + Symbol: '' + CommonCode: '62' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: failure rate in time + ConversionFactor: '' + Symbol: '' + CommonCode: '63' + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.1' + Name: pound per square inch, gauge + ConversionFactor: 7,030 696 x 10² kg/m² + Symbol: '' + CommonCode: '64' + Description: '' + conversion: + factor: 703.0696 + base_units: + - '28' +- Status: D + LevelAndCategory: '3.5' + Name: oersted + ConversionFactor: 7,957 747 x 10 A/m + Symbol: Oe + CommonCode: '66' + Description: '' + conversion: + factor: 7.957747 + base_units: + - AE +- Status: X + LevelAndCategory: '3.9' + Name: test specific scale + ConversionFactor: '' + Symbol: '' + CommonCode: '69' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: volt ampere per pound + ConversionFactor: '' + Symbol: '' + CommonCode: '71' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: watt per pound + ConversionFactor: '' + Symbol: '' + CommonCode: '72' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: ampere tum per centimetre + ConversionFactor: '' + Symbol: '' + CommonCode: '73' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: millipascal + ConversionFactor: 10⁻³ Pa + Symbol: mPa + CommonCode: '74' + Description: '' + conversion: + factor: 0.001 + base_units: + - C55 + - PAL +- Status: D + LevelAndCategory: '3.5' + Name: gauss + ConversionFactor: 10⁻⁴ T + Symbol: Gs + CommonCode: '76' + Description: '' + conversion: + factor: 0.0001 + base_units: + - D33 +- Status: '' + LevelAndCategory: '2' + Name: milli-inch + ConversionFactor: 25,4 x 10⁻⁶ m + Symbol: mil + CommonCode: '77' + Description: '' + conversion: + factor: 2.54e-05 + base_units: + - MTR +- Status: D + LevelAndCategory: '3.5' + Name: kilogauss + ConversionFactor: 10⁻¹ T + Symbol: kGs + CommonCode: '78' + Description: '' + conversion: + factor: 0.1 + base_units: + - D33 +- Status: '' + LevelAndCategory: '2' + Name: pound per square inch absolute + ConversionFactor: 7,030 696 x 10² kg/m² + Symbol: lb/in² + CommonCode: '80' + Description: '' + conversion: + factor: 703.0696 + base_units: + - '28' +- Status: '' + LevelAndCategory: '1' + Name: henry + ConversionFactor: H + Symbol: H + CommonCode: '81' + Description: '' + conversion: + factor: 1.0 + base_units: + - '81' +- Status: D + LevelAndCategory: '2' + Name: kilopound-force per square inch + ConversionFactor: 6,894 757 x 10⁶ Pa + Symbol: klbf/in² + CommonCode: '84' + Description: A unit of pressure defining the number of kilopounds force per square + inch.,Use kip per square inch (common code N20). + conversion: + factor: 6894757.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: foot pound-force + ConversionFactor: 1,355 818 J + Symbol: ft·lbf + CommonCode: '85' + Description: '' + conversion: + factor: 1.355818 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: pound per cubic foot + ConversionFactor: 1,601 846 x 10¹ kg/m³ + Symbol: lb/ft³ + CommonCode: '87' + Description: '' + conversion: + factor: 16.01846 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: poise + ConversionFactor: 0,1 Pa x s + Symbol: P + CommonCode: '89' + Description: '' + conversion: + factor: 0.1 + base_units: + - C65 + - N36 + - N37 +- Status: X + LevelAndCategory: '3.9' + Name: Saybold universal second + ConversionFactor: '' + Symbol: '' + CommonCode: '90' + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: stokes + ConversionFactor: 10⁻⁴ m²/s + Symbol: St + CommonCode: '91' + Description: '' + conversion: + factor: 0.0001 + base_units: + - S4 +- Status: X + LevelAndCategory: '3.9' + Name: calorie per cubic centimetre + ConversionFactor: '' + Symbol: '' + CommonCode: '92' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: calorie per gram + ConversionFactor: 4,186 8 x 10³ J/kg + Symbol: cal/g + CommonCode: '93' + Description: Use International Table (IT) calorie per gram (common code D75). + conversion: + factor: 4186.8 + base_units: + - J2 +- Status: X + LevelAndCategory: '3.9' + Name: curl unit + ConversionFactor: '' + Symbol: '' + CommonCode: '94' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: twenty thousand gallon (US) tankcar + ConversionFactor: '' + Symbol: '' + CommonCode: '95' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: ten thousand gallon (US) tankcar + ConversionFactor: '' + Symbol: '' + CommonCode: '96' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: ten kg drum + ConversionFactor: '' + Symbol: '' + CommonCode: '97' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: fifteen kg drum + ConversionFactor: '' + Symbol: '' + CommonCode: '98' + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: car mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1A + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: car count + ConversionFactor: '' + Symbol: '' + CommonCode: 1B + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: locomotive count + ConversionFactor: '' + Symbol: '' + CommonCode: 1C + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: caboose count + ConversionFactor: '' + Symbol: '' + CommonCode: 1D + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: empty car + ConversionFactor: '' + Symbol: '' + CommonCode: 1E + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: train mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1F + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: fuel usage gallon (US) + ConversionFactor: '' + Symbol: '' + CommonCode: 1G + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: caboose mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1H + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: fixed rate + ConversionFactor: '' + Symbol: '' + CommonCode: 1I + Description: A unit of quantity expressed as a predetermined or set rate for usage + of a facility or service. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: ton mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1J + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: locomotive mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1K + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: total car count + ConversionFactor: '' + Symbol: '' + CommonCode: 1L + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: total car mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1M + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: quarter mile + ConversionFactor: '' + Symbol: '' + CommonCode: 1X + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: radian per second + ConversionFactor: rad/s + Symbol: rad/s + CommonCode: 2A + Description: Refer ISO/TC12 SI Guide + conversion: + factor: 1.0 + base_units: + - 2A +- Status: '' + LevelAndCategory: '1' + Name: radian per second squared + ConversionFactor: rad/s² + Symbol: rad/s² + CommonCode: 2B + Description: Refer ISO/TC12 SI Guide + conversion: + factor: 1.0 + base_units: + - 2B +- Status: '' + LevelAndCategory: '2' + Name: roentgen + ConversionFactor: 2,58 x 10⁻⁴ C/kg + Symbol: R + CommonCode: 2C + Description: '' + conversion: + factor: 0.000258 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: volt AC + ConversionFactor: V + Symbol: V + CommonCode: 2G + Description: A unit of electric potential in relation to alternating current (AC). + conversion: + factor: 1.0 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: '3.1' + Name: volt DC + ConversionFactor: V + Symbol: V + CommonCode: 2H + Description: A unit of electric potential in relation to direct current (DC). + conversion: + factor: 1.0 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per hour + ConversionFactor: 2,930 711x 10⁻¹ W + Symbol: BtuIT/h + CommonCode: 2I + Description: '' + conversion: + factor: 2.930711 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: cubic centimetre per second + ConversionFactor: 10⁻⁶ m³/s + Symbol: cm³/s + CommonCode: 2J + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: cubic foot per hour + ConversionFactor: 7,865 79 x 10⁻⁶ m³/s + Symbol: ft³/h + CommonCode: 2K + Description: '' + conversion: + factor: 7.86579e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: cubic foot per minute + ConversionFactor: 4,719 474 x 10⁻⁴ m³/s + Symbol: ft³/min + CommonCode: 2L + Description: '' + conversion: + factor: 0.0004719474 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1S + Name: centimetre per second + ConversionFactor: 10⁻² m/s + Symbol: cm/s + CommonCode: 2M + Description: '' + conversion: + factor: 0.01 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '1' + Name: decibel + ConversionFactor: 0,115 129 3 Np + Symbol: dB + CommonCode: 2N + Description: '' + conversion: + factor: 0.1151293 + base_units: + - C50 +- Status: '' + LevelAndCategory: '3.6' + Name: kilobyte + ConversionFactor: '' + Symbol: kbyte + CommonCode: 2P + Description: A unit of information equal to 10³ (1000) bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilobecquerel + ConversionFactor: 10³ Bq + Symbol: kBq + CommonCode: 2Q + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 2S + Name: kilocurie + ConversionFactor: 3,7 x 10¹³ Bq + Symbol: kCi + CommonCode: 2R + Description: '' + conversion: + factor: 37000000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: megagram + ConversionFactor: 10³ kg + Symbol: Mg + CommonCode: 2U + Description: '' + conversion: + factor: 1000.0 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.8' + Name: megagram per hour + ConversionFactor: '' + Symbol: Mg/h + CommonCode: 2V + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bin + ConversionFactor: '' + Symbol: '' + CommonCode: 2W + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: metre per minute + ConversionFactor: 0,016 666 m/s + Symbol: m/min + CommonCode: 2X + Description: '' + conversion: + factor: 0.016666 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: milliroentgen + ConversionFactor: 2,58 x 10⁻⁷ C/kg + Symbol: mR + CommonCode: 2Y + Description: '' + conversion: + factor: 2.58e-07 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: millivolt + ConversionFactor: 10⁻³ V + Symbol: mV + CommonCode: 2Z + Description: '' + conversion: + factor: 0.001 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: 1S + Name: megajoule + ConversionFactor: 10⁶ J + Symbol: MJ + CommonCode: 3B + Description: '' + conversion: + factor: 1000000.0 + base_units: + - JOU +- Status: '' + LevelAndCategory: '3.9' + Name: manmonth + ConversionFactor: '' + Symbol: '' + CommonCode: 3C + Description: A unit of count defining the number of months for a person or persons + to perform an undertaking. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: pound per pound of product + ConversionFactor: '' + Symbol: '' + CommonCode: 3E + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: pound per piece of product + ConversionFactor: '' + Symbol: '' + CommonCode: 3G + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: kilogram per kilogram of product + ConversionFactor: '' + Symbol: '' + CommonCode: 3H + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: kilogram per piece of product + ConversionFactor: '' + Symbol: '' + CommonCode: 3I + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bobbin + ConversionFactor: '' + Symbol: '' + CommonCode: 4A + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: cap + ConversionFactor: '' + Symbol: '' + CommonCode: 4B + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: centistokes + ConversionFactor: 10⁻⁶ m²/s + Symbol: cSt + CommonCode: 4C + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - S4 +- Status: X + LevelAndCategory: '3.2' + Name: twenty pack + ConversionFactor: '' + Symbol: '' + CommonCode: 4E + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: microlitre + ConversionFactor: 10⁻⁹ m³ + Symbol: µl + CommonCode: 4G + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: micrometre (micron) + ConversionFactor: 10⁻⁶ m + Symbol: µm + CommonCode: 4H + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1S + Name: milliampere + ConversionFactor: 10⁻³ A + Symbol: mA + CommonCode: 4K + Description: '' + conversion: + factor: 0.001 + base_units: + - AMP +- Status: '' + LevelAndCategory: '3.6' + Name: megabyte + ConversionFactor: '' + Symbol: Mbyte + CommonCode: 4L + Description: A unit of information equal to 10⁶ (1000000) bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: milligram per hour + ConversionFactor: 2,777 78 x 10⁻¹⁰ kg/s + Symbol: mg/h + CommonCode: 4M + Description: '' + conversion: + factor: 2.77778e-10 + base_units: + - KGS +- Status: '' + LevelAndCategory: 1S + Name: megabecquerel + ConversionFactor: 10⁶ Bq + Symbol: MBq + CommonCode: 4N + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: microfarad + ConversionFactor: 10⁻⁶ F + Symbol: µF + CommonCode: 4O + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - FAR +- Status: '' + LevelAndCategory: '1' + Name: newton per metre + ConversionFactor: N/m + Symbol: N/m + CommonCode: 4P + Description: '' + conversion: + factor: 1.0 + base_units: + - 4P +- Status: '' + LevelAndCategory: '2' + Name: ounce inch + ConversionFactor: 7,200 778 x 10⁻⁴ kg x m + Symbol: oz·in + CommonCode: 4Q + Description: '' + conversion: + factor: 0.0007200778 + base_units: + - M94 +- Status: '' + LevelAndCategory: '2' + Name: ounce foot + ConversionFactor: 8,640 934 x 10⁻³ kg x m + Symbol: oz·ft + CommonCode: 4R + Description: '' + conversion: + factor: 0.008640934 + base_units: + - M94 +- Status: '' + LevelAndCategory: 1S + Name: picofarad + ConversionFactor: 10⁻¹² F + Symbol: pF + CommonCode: 4T + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - FAR +- Status: '' + LevelAndCategory: '2' + Name: pound per hour + ConversionFactor: 1,259 979 x 10⁻⁴ kg/s + Symbol: lb/h + CommonCode: 4U + Description: '' + conversion: + factor: 0.0001259979 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ton (US) per hour + ConversionFactor: 2,519 958 x 10⁻¹ kg/s + Symbol: ton (US) /h + CommonCode: 4W + Description: '' + conversion: + factor: 0.2519958 + base_units: + - KGS +- Status: '' + LevelAndCategory: 1M + Name: kilolitre per hour + ConversionFactor: 2,777 78 x 10⁻⁴ m³/s + Symbol: kl/h + CommonCode: 4X + Description: '' + conversion: + factor: 0.000277778 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: barrel (US) per minute + ConversionFactor: 2,649 79 x 10⁻³ m³/s + Symbol: barrel (US)/min + CommonCode: 5A + Description: '' + conversion: + factor: 0.00264979 + base_units: + - MQS +- Status: '' + LevelAndCategory: '3.9' + Name: batch + ConversionFactor: '' + Symbol: '' + CommonCode: 5B + Description: 'A unit of count defining the number of batches (batch: quantity of + material produced in one operation or number of animals or persons coming at once).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: gallon(US) per thousand + ConversionFactor: '' + Symbol: '' + CommonCode: 5C + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: MMSCF/day + ConversionFactor: '' + Symbol: '' + CommonCode: 5E + Description: A unit of volume equal to one million (1000000) cubic feet of gas per + day. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: pound per thousand + ConversionFactor: '' + Symbol: '' + CommonCode: 5F + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: pump + ConversionFactor: '' + Symbol: '' + CommonCode: 5G + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: stage + ConversionFactor: '' + Symbol: '' + CommonCode: 5H + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '2' + Name: standard cubic foot + ConversionFactor: 4,672 m³ + Symbol: std + CommonCode: 5I + Description: Use standard (common code WSD) + conversion: + factor: 4.672 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.5' + Name: hydraulic horse power + ConversionFactor: '' + Symbol: '' + CommonCode: 5J + Description: A unit of power defining the hydraulic horse power delivered by a fluid + pump depending on the viscosity of the fluid. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: count per minute + ConversionFactor: '' + Symbol: '' + CommonCode: 5K + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: seismic level + ConversionFactor: '' + Symbol: '' + CommonCode: 5P + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: seismic line + ConversionFactor: '' + Symbol: '' + CommonCode: 5Q + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: 15 °C calorie + ConversionFactor: 4,188 46 J + Symbol: cal₁₅ + CommonCode: A1 + Description: '' + conversion: + factor: 4.18846 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: ampere square metre per joule second + ConversionFactor: "(A x s)/kg" + Symbol: A·m²/(J·s) + CommonCode: A10 + Description: '' + conversion: + factor: 1.0 + base_units: + - A10 +- Status: '' + LevelAndCategory: '1' + Name: angstrom + ConversionFactor: 10⁻¹⁰ m + Symbol: Å + CommonCode: A11 + Description: '' + conversion: + factor: 1.0e-10 + base_units: + - MTR +- Status: '' + LevelAndCategory: '1' + Name: astronomical unit + ConversionFactor: 1,495 978 70 x 10¹¹ m + Symbol: ua + CommonCode: A12 + Description: '' + conversion: + factor: 149597870000.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1S + Name: attojoule + ConversionFactor: 10⁻¹⁸ J + Symbol: aJ + CommonCode: A13 + Description: '' + conversion: + factor: 1.0e-18 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: barn + ConversionFactor: 10⁻²⁸ m² + Symbol: b + CommonCode: A14 + Description: '' + conversion: + factor: 1.0e-28 + base_units: + - MTK +- Status: '' + LevelAndCategory: '1' + Name: barn per electronvolt + ConversionFactor: 6,241 51 x 10⁻¹⁰ m²/J + Symbol: b/eV + CommonCode: A15 + Description: '' + conversion: + factor: 6.241 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: barn per steradian electronvolt + ConversionFactor: 6,241 51 x 10⁻¹⁰ m²/(sr xJ) + Symbol: b/(sr·eV) + CommonCode: A16 + Description: '' + conversion: + factor: 6.241 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: barn per steradian + ConversionFactor: 1 x 10⁻²⁸ m²/sr + Symbol: b/sr + CommonCode: A17 + Description: '' + conversion: + factor: 1.0e-28 + base_units: + - D24 +- Status: '' + LevelAndCategory: '1' + Name: becquerel per kilogram + ConversionFactor: 27,027 x 10⁻¹² Ci/kg + Symbol: Bq/kg + CommonCode: A18 + Description: '' + conversion: + factor: 2.7027e-11 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: becquerel per cubic metre + ConversionFactor: Bq/m³ + Symbol: Bq/m³ + CommonCode: A19 + Description: '' + conversion: + factor: 1.0 + base_units: + - A19 +- Status: '' + LevelAndCategory: 1S + Name: ampere per centimetre + ConversionFactor: 10² A/m + Symbol: A/cm + CommonCode: A2 + Description: '' + conversion: + factor: 100.0 + base_units: + - AE +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per second square foot degree Rankine + ConversionFactor: 20 441,7 W/(m² x K) + Symbol: BtuIT/(s·ft²·°R) + CommonCode: A20 + Description: '' + conversion: + factor: 20441.7 + base_units: + - D55 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per pound degree Rankine + ConversionFactor: 4 186,8 J/(kg x K) + Symbol: BtuIT/(lb·°R) + CommonCode: A21 + Description: '' + conversion: + factor: 4186.8 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per second foot degree Rankine + ConversionFactor: 6 230,64 W/(m x K) + Symbol: BtuIT/(s·ft·°R) + CommonCode: A22 + Description: '' + conversion: + factor: 6230.64 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per hour square foot degree Rankine + ConversionFactor: 5,678 26 W/ (m² x K) + Symbol: BtuIT/(h·ft²·°R) + CommonCode: A23 + Description: '' + conversion: + factor: 5.67826 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: candela per square metre + ConversionFactor: cd/m² + Symbol: cd/m² + CommonCode: A24 + Description: '' + conversion: + factor: 1.0 + base_units: + - A24 +- Status: D + LevelAndCategory: '2' + Name: cheval vapeur + ConversionFactor: 7,354 988 x 10² W + Symbol: CV + CommonCode: A25 + Description: 'Synonym: metric horse power' + conversion: + factor: 735.4988 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '1' + Name: coulomb metre + ConversionFactor: A x s x m + Symbol: C·m + CommonCode: A26 + Description: '' + conversion: + factor: 1.0 + base_units: + - A26 +- Status: '' + LevelAndCategory: '1' + Name: coulomb metre squared per volt + ConversionFactor: A² x s⁴/kg + Symbol: C·m²/V + CommonCode: A27 + Description: '' + conversion: + factor: 1.0 + base_units: + - A27 +- Status: '' + LevelAndCategory: 1S + Name: coulomb per cubic centimetre + ConversionFactor: 10⁶ C/m³ + Symbol: C/cm³ + CommonCode: A28 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A29 +- Status: '' + LevelAndCategory: '1' + Name: coulomb per cubic metre + ConversionFactor: C/m³ + Symbol: C/m³ + CommonCode: A29 + Description: '' + conversion: + factor: 1.0 + base_units: + - A29 +- Status: '' + LevelAndCategory: 1S + Name: ampere per millimetre + ConversionFactor: 10³ A/m + Symbol: A/mm + CommonCode: A3 + Description: '' + conversion: + factor: 1000.0 + base_units: + - AE +- Status: '' + LevelAndCategory: 1S + Name: coulomb per cubic millimetre + ConversionFactor: 10⁹ C/m³ + Symbol: C/mm³ + CommonCode: A30 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - A29 +- Status: '' + LevelAndCategory: '1' + Name: coulomb per kilogram second + ConversionFactor: A/kg + Symbol: C/(kg·s) + CommonCode: A31 + Description: '' + conversion: + factor: 1.0 + base_units: + - A31 +- Status: '' + LevelAndCategory: '1' + Name: coulomb per mole + ConversionFactor: A x s/mol + Symbol: C/mol + CommonCode: A32 + Description: '' + conversion: + factor: 1.0 + base_units: + - A32 +- Status: '' + LevelAndCategory: 1S + Name: coulomb per square centimetre + ConversionFactor: 10⁴ C/m² + Symbol: C/cm² + CommonCode: A33 + Description: '' + conversion: + factor: 10000.0 + base_units: + - A34 +- Status: '' + LevelAndCategory: '1' + Name: coulomb per square metre + ConversionFactor: C/m² + Symbol: C/m² + CommonCode: A34 + Description: '' + conversion: + factor: 1.0 + base_units: + - A34 +- Status: '' + LevelAndCategory: 1S + Name: coulomb per square millimetre + ConversionFactor: 10⁶ C/m² + Symbol: C/mm² + CommonCode: A35 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A34 +- Status: '' + LevelAndCategory: 1S + Name: cubic centimetre per mole + ConversionFactor: 10⁻⁶ m³/mol + Symbol: cm³/mol + CommonCode: A36 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - A40 +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre per mole + ConversionFactor: 10⁻³ m³/mol + Symbol: dm³/mol + CommonCode: A37 + Description: '' + conversion: + factor: 0.001 + base_units: + - A40 +- Status: '' + LevelAndCategory: '1' + Name: cubic metre per coulomb + ConversionFactor: m³/A x s + Symbol: m³/C + CommonCode: A38 + Description: '' + conversion: + factor: 1.0 + base_units: + - A38 +- Status: '' + LevelAndCategory: '1' + Name: cubic metre per kilogram + ConversionFactor: m³/kg + Symbol: m³/kg + CommonCode: A39 + Description: '' + conversion: + factor: 1.0 + base_units: + - A39 +- Status: '' + LevelAndCategory: 1S + Name: ampere per square centimetre + ConversionFactor: 10⁴ A/m² + Symbol: A/cm² + CommonCode: A4 + Description: '' + conversion: + factor: 10000.0 + base_units: + - A41 +- Status: '' + LevelAndCategory: '1' + Name: cubic metre per mole + ConversionFactor: m³/mol + Symbol: m³/mol + CommonCode: A40 + Description: '' + conversion: + factor: 1.0 + base_units: + - A40 +- Status: '' + LevelAndCategory: '1' + Name: ampere per square metre + ConversionFactor: A/m² + Symbol: A/m² + CommonCode: A41 + Description: '' + conversion: + factor: 1.0 + base_units: + - A41 +- Status: '' + LevelAndCategory: '2' + Name: curie per kilogram + ConversionFactor: 3,7 x 10¹⁰ Bq/kg + Symbol: Ci/kg + CommonCode: A42 + Description: '' + conversion: + factor: 37000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.4' + Name: deadweight tonnage + ConversionFactor: '' + Symbol: dwt + CommonCode: A43 + Description: A unit of mass defining the difference between the weight of a ship + when completely empty and its weight when completely loaded, expressed as the + number of tons. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: decalitre + ConversionFactor: 10⁻² m³ + Symbol: dal + CommonCode: A44 + Description: '' + conversion: + factor: 0.01 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: decametre + ConversionFactor: 10 m + Symbol: dam + CommonCode: A45 + Description: '' + conversion: + factor: 10.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: '3.5' + Name: decitex + ConversionFactor: '' + Symbol: dtex (g/10km) + CommonCode: A47 + Description: A unit of yarn density. One decitex equals a mass of 1 gram per 10 + kilometres of length. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: degree Rankine + ConversionFactor: 5/9 x K + Symbol: "°R" + CommonCode: A48 + Description: 'Refer ISO 80000-5 (Quantities and units — Part 5: Thermodynamics)' + conversion: + factor: 5.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: denier + ConversionFactor: '' + Symbol: den (g/9 km) + CommonCode: A49 + Description: A unit of yarn density. One denier equals a mass of 1 gram per 9 kilometres + of length. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: ampere square metre + ConversionFactor: A x m² + Symbol: A·m² + CommonCode: A5 + Description: '' + conversion: + factor: 1.0 + base_units: + - A5 +- Status: D + LevelAndCategory: '2' + Name: dyne second per cubic centimetre + ConversionFactor: 10 Pa x s/m + Symbol: dyn·s/cm³ + CommonCode: A50 + Description: '' + conversion: + factor: 10.0 + base_units: + - C67 +- Status: D + LevelAndCategory: '2' + Name: dyne second per centimetre + ConversionFactor: 10⁻³ N x s/m + Symbol: dyn·s/cm + CommonCode: A51 + Description: '' + conversion: + factor: 0.001 + base_units: + - C58 +- Status: D + LevelAndCategory: '2' + Name: dyne second per centimetre to the fifth power + ConversionFactor: 10⁵ Pa x s/m³ + Symbol: dyn·s/cm⁵ + CommonCode: A52 + Description: '' + conversion: + factor: 100000.0 + base_units: + - C66 +- Status: '' + LevelAndCategory: '1' + Name: electronvolt + ConversionFactor: 1,602 176 487 x 10⁻¹⁹ J + Symbol: eV + CommonCode: A53 + Description: '' + conversion: + factor: 1.602176487e-19 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: electronvolt per metre + ConversionFactor: 1,602 176 487 x 10⁻¹⁹ J/m + Symbol: eV/m + CommonCode: A54 + Description: '' + conversion: + factor: 1.602176487e-19 + base_units: + - B12 +- Status: '' + LevelAndCategory: '1' + Name: electronvolt square metre + ConversionFactor: 1,602 176 487 x 10⁻¹⁹ J x m² + Symbol: eV·m² + CommonCode: A55 + Description: '' + conversion: + factor: 1.602176487e-19 + base_units: + - D73 +- Status: '' + LevelAndCategory: '1' + Name: electronvolt square metre per kilogram + ConversionFactor: 1,602 176 487 x 10⁻¹⁹ J x m²/kg + Symbol: eV·m²/kg + CommonCode: A56 + Description: '' + conversion: + factor: 1.602176487e-19 + base_units: + - B20 +- Status: D + LevelAndCategory: '2' + Name: erg + ConversionFactor: 10⁻⁷J + Symbol: erg + CommonCode: A57 + Description: '' + conversion: + factor: 1.0e-07 + base_units: + - JOU +- Status: D + LevelAndCategory: '2' + Name: erg per centimetre + ConversionFactor: 10⁻⁵ J/m + Symbol: erg/cm + CommonCode: A58 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - B12 +- Status: '' + LevelAndCategory: '3.9' + Name: 8-part cloud cover + ConversionFactor: '' + Symbol: '' + CommonCode: A59 + Description: 'A unit of count defining the number of eighth-parts as a measure of + the celestial dome cloud coverage.,Synonym: OKTA , OCTA' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: ampere per square metre kelvin squared + ConversionFactor: A/(m² x K²) + Symbol: A/(m²·K²) + CommonCode: A6 + Description: '' + conversion: + factor: 1.0 + base_units: + - A6 +- Status: D + LevelAndCategory: '2' + Name: erg per cubic centimetre + ConversionFactor: 10⁻¹ J/m³ + Symbol: erg/cm³ + CommonCode: A60 + Description: '' + conversion: + factor: 0.1 + base_units: + - B8 +- Status: D + LevelAndCategory: '2' + Name: erg per gram + ConversionFactor: 10⁻⁴ J/kg + Symbol: erg/g + CommonCode: A61 + Description: '' + conversion: + factor: 0.0001 + base_units: + - J2 +- Status: D + LevelAndCategory: '2' + Name: erg per gram second + ConversionFactor: 10⁻⁴ W/kg + Symbol: erg/g·s + CommonCode: A62 + Description: '' + conversion: + factor: 0.0001 + base_units: + - WA +- Status: D + LevelAndCategory: '2' + Name: erg per second + ConversionFactor: 10⁻⁷ W + Symbol: erg/s + CommonCode: A63 + Description: '' + conversion: + factor: 1.0e-07 + base_units: + - D46 + - P14 + - WTT +- Status: D + LevelAndCategory: '2' + Name: erg per second square centimetre + ConversionFactor: 10⁻³ W/m² + Symbol: erg/(s·cm²) + CommonCode: A64 + Description: '' + conversion: + factor: 0.001 + base_units: + - D54 +- Status: D + LevelAndCategory: '2' + Name: erg per square centimetre second + ConversionFactor: 10⁻³ W/m² + Symbol: erg/(cm²·s) + CommonCode: A65 + Description: '' + conversion: + factor: 0.001 + base_units: + - D54 +- Status: D + LevelAndCategory: '2' + Name: erg square centimetre + ConversionFactor: 10⁻¹¹ J x m² + Symbol: erg·cm² + CommonCode: A66 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - D73 +- Status: D + LevelAndCategory: '2' + Name: erg square centimetre per gram + ConversionFactor: 10⁻⁸ J x m²/kg + Symbol: erg·cm²/g + CommonCode: A67 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - B20 +- Status: '' + LevelAndCategory: 1S + Name: exajoule + ConversionFactor: 10¹⁸ J + Symbol: EJ + CommonCode: A68 + Description: '' + conversion: + factor: 1.0e+18 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: farad per metre + ConversionFactor: kg⁻¹ x m⁻³ x s⁴ x A² + Symbol: F/m + CommonCode: A69 + Description: '' + conversion: + factor: 1.0 + base_units: + - A69 +- Status: '' + LevelAndCategory: 1S + Name: ampere per square millimetre + ConversionFactor: 10⁶ A/m² + Symbol: A/mm² + CommonCode: A7 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A41 +- Status: '' + LevelAndCategory: 1S + Name: femtojoule + ConversionFactor: 10⁻¹⁵ J + Symbol: fJ + CommonCode: A70 + Description: '' + conversion: + factor: 1.0e-15 + base_units: + - JOU +- Status: '' + LevelAndCategory: 1S + Name: femtometre + ConversionFactor: 10⁻¹⁵ m + Symbol: fm + CommonCode: A71 + Description: '' + conversion: + factor: 1.0e-15 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: foot per second squared + ConversionFactor: 0,304 8 m/s² + Symbol: ft/s² + CommonCode: A73 + Description: '' + conversion: + factor: 0.3048 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: '2' + Name: foot pound-force per second + ConversionFactor: 1,355 818 W + Symbol: ft·lbf/s + CommonCode: A74 + Description: '' + conversion: + factor: 1.355818 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '3.4' + Name: freight ton + ConversionFactor: '' + Symbol: '' + CommonCode: A75 + Description: A unit of information typically used for billing purposes, defined + as either the number of metric tons or the number of cubic metres, whichever is + the larger. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gal + ConversionFactor: 10⁻² m/s² + Symbol: Gal + CommonCode: A76 + Description: '' + conversion: + factor: 0.01 + base_units: + - MSK + - P79 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of displacement + ConversionFactor: '' + Symbol: '' + CommonCode: A77 + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of electric current + ConversionFactor: '' + Symbol: '' + CommonCode: A78 + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of electric charge + ConversionFactor: '' + Symbol: '' + CommonCode: A79 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: ampere second + ConversionFactor: C + Symbol: A·s + CommonCode: A8 + Description: '' + conversion: + factor: 1.0 + base_units: + - A8 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of electric field strength + ConversionFactor: '' + Symbol: '' + CommonCode: A80 + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of electric polarization + ConversionFactor: '' + Symbol: '' + CommonCode: A81 + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of electric potential + ConversionFactor: '' + Symbol: '' + CommonCode: A82 + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.5' + Name: Gaussian CGS (Centimetre-Gram-Second system) unit of magnetization + ConversionFactor: '' + Symbol: '' + CommonCode: A83 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gigacoulomb per cubic metre + ConversionFactor: 10⁹ C/m³ + Symbol: GC/m³ + CommonCode: A84 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: gigaelectronvolt + ConversionFactor: 10⁹ eV + Symbol: GeV + CommonCode: A85 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: gigahertz + ConversionFactor: 10⁹ Hz + Symbol: GHz + CommonCode: A86 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: gigaohm + ConversionFactor: 10⁹ Ω + Symbol: GΩ + CommonCode: A87 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: gigaohm metre + ConversionFactor: 10⁹ Ω x m + Symbol: GΩ·m + CommonCode: A88 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: gigapascal + ConversionFactor: 10⁹ Pa + Symbol: GPa + CommonCode: A89 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.9' + Name: rate + ConversionFactor: '' + Symbol: '' + CommonCode: A9 + Description: A unit of quantity expressed as a rate for usage of a facility or service. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gigawatt + ConversionFactor: 10⁹ W + Symbol: GW + CommonCode: A90 + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: gon + ConversionFactor: 1,570 796 x 10⁻² rad + Symbol: gon + CommonCode: A91 + Description: 'Synonym: grade' + conversion: + factor: 0.01570796 + base_units: + - C81 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic metre + ConversionFactor: 10⁻³ kg/m³ + Symbol: g/m³ + CommonCode: A93 + Description: '' + conversion: + factor: 0.001 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1S + Name: gram per mole + ConversionFactor: 10⁻³ kg/mol + Symbol: g/mol + CommonCode: A94 + Description: '' + conversion: + factor: 0.001 + base_units: + - D74 +- Status: '' + LevelAndCategory: '1' + Name: gray + ConversionFactor: m²/s² + Symbol: Gy + CommonCode: A95 + Description: '' + conversion: + factor: 1.0 + base_units: + - A95 + - D13 +- Status: '' + LevelAndCategory: '1' + Name: gray per second + ConversionFactor: m²/s³ + Symbol: Gy/s + CommonCode: A96 + Description: '' + conversion: + factor: 1.0 + base_units: + - A96 +- Status: '' + LevelAndCategory: 1S + Name: hectopascal + ConversionFactor: 10² Pa + Symbol: hPa + CommonCode: A97 + Description: '' + conversion: + factor: 100.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '1' + Name: henry per metre + ConversionFactor: H/m + Symbol: H/m + CommonCode: A98 + Description: '' + conversion: + factor: 1.0 + base_units: + - A98 +- Status: '' + LevelAndCategory: '3.6' + Name: bit + ConversionFactor: '' + Symbol: bit + CommonCode: A99 + Description: A unit of information equal to one binary digit. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: ball + ConversionFactor: '' + Symbol: '' + CommonCode: AA + Description: 'A unit of count defining the number of balls (ball: object formed + in the shape of sphere).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: bulk pack + ConversionFactor: '' + Symbol: pk + CommonCode: AB + Description: A unit of count defining the number of items per bulk pack. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: acre + ConversionFactor: 4 046,873 m² + Symbol: acre + CommonCode: ACR + Description: '' + conversion: + factor: 4046.873 + base_units: + - MTK +- Status: '' + LevelAndCategory: '3.2' + Name: activity + ConversionFactor: '' + Symbol: '' + CommonCode: ACT + Description: 'A unit of count defining the number of activities (activity: a unit + of work or action).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: byte + ConversionFactor: '' + Symbol: byte + CommonCode: AD + Description: A unit of information equal to 8 bits. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: ampere per metre + ConversionFactor: A/m + Symbol: A/m + CommonCode: AE + Description: '' + conversion: + factor: 1.0 + base_units: + - AE +- Status: '' + LevelAndCategory: '3.5' + Name: additional minute + ConversionFactor: '' + Symbol: '' + CommonCode: AH + Description: A unit of time defining the number of minutes in addition to the referenced + minutes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: average minute per call + ConversionFactor: '' + Symbol: '' + CommonCode: AI + Description: A unit of count defining the number of minutes for the average interval + of a call. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: cop + ConversionFactor: '' + Symbol: '' + CommonCode: AJ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: fathom + ConversionFactor: 1,828 8 m + Symbol: fth + CommonCode: AK + Description: '' + conversion: + factor: 1.8288 + base_units: + - MTR +- Status: '' + LevelAndCategory: '3.5' + Name: access line + ConversionFactor: '' + Symbol: '' + CommonCode: AL + Description: A unit of count defining the number of telephone access lines. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: ampoule + ConversionFactor: '' + Symbol: '' + CommonCode: AM + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: ampere hour + ConversionFactor: 3,6 x 10³ C + Symbol: A·h + CommonCode: AMH + Description: A unit of electric charge defining the amount of charge accumulated + by a steady flow of one ampere for one hour. + conversion: + factor: 3600.0 + base_units: + - A8 +- Status: '' + LevelAndCategory: '1' + Name: ampere + ConversionFactor: A + Symbol: A + CommonCode: AMP + Description: '' + conversion: + factor: 1.0 + base_units: + - AMP +- Status: '' + LevelAndCategory: '2' + Name: year + ConversionFactor: 3,155 76 x 10⁷ s + Symbol: y + CommonCode: ANN + Description: 'Unit of time equal to 365,25 days.,Synonym: Julian year' + conversion: + factor: 31557600.0 + base_units: + - H04 + - SEC +- Status: X + LevelAndCategory: '3.1' + Name: aluminium pound only + ConversionFactor: '' + Symbol: '' + CommonCode: AP + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: troy ounce or apothecary ounce + ConversionFactor: 3,110 348 x 10⁻³ kg + Symbol: tr oz + CommonCode: APZ + Description: '' + conversion: + factor: 0.003110348 + base_units: [] +- Status: '' + LevelAndCategory: '3.9' + Name: anti-hemophilic factor (AHF) unit + ConversionFactor: '' + Symbol: '' + CommonCode: AQ + Description: A unit of measure for blood potency (US). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: suppository + ConversionFactor: '' + Symbol: '' + CommonCode: AR + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2.0' + Name: are + ConversionFactor: 10² m² + Symbol: a + CommonCode: ARE + Description: 'Synonym: square decametre' + conversion: + factor: 100.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: '3.9' + Name: assortment + ConversionFactor: '' + Symbol: '' + CommonCode: AS + Description: 'A unit of count defining the number of assortments (assortment: set + of items grouped in a mixed collection).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: alcoholic strength by mass + ConversionFactor: '' + Symbol: '' + CommonCode: ASM + Description: A unit of mass defining the alcoholic strength of a liquid. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: alcoholic strength by volume + ConversionFactor: '' + Symbol: '' + CommonCode: ASU + Description: A unit of volume defining the alcoholic strength of a liquid (e.g. + spirit, wine, beer, etc), often at a specific temperature. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: standard atmosphere + ConversionFactor: 1 013 25 Pa + Symbol: atm + CommonCode: ATM + Description: '' + conversion: + factor: 101325.0 + base_units: + - C55 + - PAL +- Status: D + LevelAndCategory: '2' + Name: technical atmosphere + ConversionFactor: 98 066,5 Pa + Symbol: at + CommonCode: ATT + Description: '' + conversion: + factor: 98066.5 + base_units: + - C55 + - PAL +- Status: X + LevelAndCategory: '3.3' + Name: capsule + ConversionFactor: '' + Symbol: '' + CommonCode: AV + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: powder filled vial + ConversionFactor: '' + Symbol: '' + CommonCode: AW + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: american wire gauge + ConversionFactor: '' + Symbol: '' + CommonCode: AWG + Description: A unit of distance used for measuring the diameter of small tubes or + wires such as the outer diameter of hypotermic or suture needles. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: assembly + ConversionFactor: '' + Symbol: '' + CommonCode: AY + Description: 'A unit of count defining the number of assemblies (assembly: items + that consist of component parts).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per pound + ConversionFactor: 2 326 J/kg + Symbol: BtuIT/lb + CommonCode: AZ + Description: '' + conversion: + factor: 2326.0 + base_units: + - J2 +- Status: X + LevelAndCategory: '3.9' + Name: Btu per cubic foot + ConversionFactor: '' + Symbol: BTU/ft³ + CommonCode: B0 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: barrel (US) per day + ConversionFactor: 1,840 13 x 10⁻⁶ m³/s + Symbol: barrel (US)/d + CommonCode: B1 + Description: '' + conversion: + factor: 1.84013e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '3.6' + Name: bit per second + ConversionFactor: '' + Symbol: bit/s + CommonCode: B10 + Description: A unit of information equal to one binary digit per second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: joule per kilogram kelvin + ConversionFactor: J/(kg x K) + Symbol: J/(kg·K) + CommonCode: B11 + Description: '' + conversion: + factor: 1.0 + base_units: + - B11 +- Status: '' + LevelAndCategory: '1' + Name: joule per metre + ConversionFactor: J/m + Symbol: J/m + CommonCode: B12 + Description: '' + conversion: + factor: 1.0 + base_units: + - B12 +- Status: '' + LevelAndCategory: '1' + Name: joule per square metre + ConversionFactor: J/m² + Symbol: J/m² + CommonCode: B13 + Description: 'Synonym: joule per metre squared' + conversion: + factor: 1.0 + base_units: + - B13 +- Status: '' + LevelAndCategory: '1' + Name: joule per metre to the fourth power + ConversionFactor: J/m⁴ + Symbol: J/m⁴ + CommonCode: B14 + Description: '' + conversion: + factor: 1.0 + base_units: + - B14 +- Status: '' + LevelAndCategory: '1' + Name: joule per mole + ConversionFactor: J/mol + Symbol: J/mol + CommonCode: B15 + Description: '' + conversion: + factor: 1.0 + base_units: + - B15 +- Status: '' + LevelAndCategory: '1' + Name: joule per mole kelvin + ConversionFactor: J/(mol x K) + Symbol: J/(mol·K) + CommonCode: B16 + Description: '' + conversion: + factor: 1.0 + base_units: + - B16 +- Status: '' + LevelAndCategory: '3.9' + Name: credit + ConversionFactor: '' + Symbol: '' + CommonCode: B17 + Description: A unit of count defining the number of entries made to the credit side + of an account. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: joule second + ConversionFactor: J x s + Symbol: J·s + CommonCode: B18 + Description: '' + conversion: + factor: 1.0 + base_units: + - B18 +- Status: '' + LevelAndCategory: '3.7' + Name: digit + ConversionFactor: '' + Symbol: '' + CommonCode: B19 + Description: A unit of information defining the quantity of numerals used to form + a number. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: bunk + ConversionFactor: '' + Symbol: '' + CommonCode: B2 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: joule square metre per kilogram + ConversionFactor: J x m²/kg + Symbol: J·m²/kg + CommonCode: B20 + Description: '' + conversion: + factor: 1.0 + base_units: + - B20 +- Status: '' + LevelAndCategory: '1' + Name: kelvin per watt + ConversionFactor: K/W + Symbol: K/W + CommonCode: B21 + Description: '' + conversion: + factor: 1.0 + base_units: + - B21 +- Status: '' + LevelAndCategory: 1S + Name: kiloampere + ConversionFactor: 10³ A + Symbol: kA + CommonCode: B22 + Description: '' + conversion: + factor: 1000.0 + base_units: + - AMP +- Status: '' + LevelAndCategory: 1S + Name: kiloampere per square metre + ConversionFactor: 10³ A/m² + Symbol: kA/m² + CommonCode: B23 + Description: '' + conversion: + factor: 1000.0 + base_units: + - A41 +- Status: '' + LevelAndCategory: 1S + Name: kiloampere per metre + ConversionFactor: 10³ A/m + Symbol: kA/m + CommonCode: B24 + Description: '' + conversion: + factor: 1000.0 + base_units: + - AE +- Status: '' + LevelAndCategory: 1S + Name: kilobecquerel per kilogram + ConversionFactor: 10³ Bq/kg + Symbol: kBq/kg + CommonCode: B25 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: kilocoulomb + ConversionFactor: 10³ C + Symbol: kC + CommonCode: B26 + Description: '' + conversion: + factor: 1000.0 + base_units: + - A8 +- Status: '' + LevelAndCategory: 1S + Name: kilocoulomb per cubic metre + ConversionFactor: 10³ C/m³ + Symbol: kC/m³ + CommonCode: B27 + Description: '' + conversion: + factor: 1000.0 + base_units: + - A29 +- Status: '' + LevelAndCategory: 1S + Name: kilocoulomb per square metre + ConversionFactor: 10³ C/m² + Symbol: kC/m² + CommonCode: B28 + Description: '' + conversion: + factor: 1000.0 + base_units: + - A34 +- Status: '' + LevelAndCategory: 1S + Name: kiloelectronvolt + ConversionFactor: 10³ eV + Symbol: keV + CommonCode: B29 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: batting pound + ConversionFactor: '' + Symbol: '' + CommonCode: B3 + Description: A unit of mass defining the number of pounds of wadded fibre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: gibibit + ConversionFactor: '' + Symbol: Gibit + CommonCode: B30 + Description: A unit of information equal to 2³⁰ bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: kilogram metre per second + ConversionFactor: kg x m/s + Symbol: kg·m/s + CommonCode: B31 + Description: '' + conversion: + factor: 1.0 + base_units: + - B31 +- Status: '' + LevelAndCategory: '1' + Name: kilogram metre squared + ConversionFactor: kg x m² + Symbol: kg·m² + CommonCode: B32 + Description: '' + conversion: + factor: 1.0 + base_units: + - B32 +- Status: '' + LevelAndCategory: '1' + Name: kilogram metre squared per second + ConversionFactor: kg x m²/s + Symbol: kg·m²/s + CommonCode: B33 + Description: '' + conversion: + factor: 1.0 + base_units: + - B33 +- Status: '' + LevelAndCategory: 1S + Name: kilogram per cubic decimetre + ConversionFactor: 10³ kg/m³ + Symbol: kg/dm³ + CommonCode: B34 + Description: '' + conversion: + factor: 1000.0 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1S + Name: kilogram per litre + ConversionFactor: 10³ kg/m³ + Symbol: kg/l or kg/L + CommonCode: B35 + Description: '' + conversion: + factor: 1000.0 + base_units: + - GL + - KMQ +- Status: D + LevelAndCategory: '2' + Name: calorie (thermochemical) per gram + ConversionFactor: 4 184 J/kg + Symbol: calth/g + CommonCode: B36 + Description: '' + conversion: + factor: 4184.0 + base_units: + - J2 +- Status: D + LevelAndCategory: '2' + Name: kilogram-force + ConversionFactor: 9,806 65 N + Symbol: kgf + CommonCode: B37 + Description: '' + conversion: + factor: 9.80665 + base_units: [] +- Status: D + LevelAndCategory: '2' + Name: kilogram-force metre + ConversionFactor: 9,806 65 N x m + Symbol: kgf·m + CommonCode: B38 + Description: '' + conversion: + factor: 9.80665 + base_units: + - NU +- Status: D + LevelAndCategory: '2' + Name: kilogram-force metre per second + ConversionFactor: 9,806 65 W + Symbol: kgf·m/s + CommonCode: B39 + Description: '' + conversion: + factor: 9.80665 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '3.5' + Name: barrel, imperial + ConversionFactor: '' + Symbol: '' + CommonCode: B4 + Description: A unit of volume used to measure beer. One beer barrel equals 36 imperial + gallons. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: kilogram-force per square metre + ConversionFactor: 9,806 65 Pa + Symbol: kgf/m² + CommonCode: B40 + Description: '' + conversion: + factor: 9.80665 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1S + Name: kilojoule per kelvin + ConversionFactor: 10³ J/K + Symbol: kJ/K + CommonCode: B41 + Description: '' + conversion: + factor: 1000.0 + base_units: + - JE +- Status: '' + LevelAndCategory: 1S + Name: kilojoule per kilogram + ConversionFactor: 10³ J/kg + Symbol: kJ/kg + CommonCode: B42 + Description: '' + conversion: + factor: 1000.0 + base_units: + - J2 +- Status: '' + LevelAndCategory: 1S + Name: kilojoule per kilogram kelvin + ConversionFactor: 10³ J/(kg x K) + Symbol: kJ/(kg·K) + CommonCode: B43 + Description: '' + conversion: + factor: 1000.0 + base_units: + - B11 +- Status: '' + LevelAndCategory: 1S + Name: kilojoule per mole + ConversionFactor: 10³ J/mol + Symbol: kJ/mol + CommonCode: B44 + Description: '' + conversion: + factor: 1000.0 + base_units: + - B15 +- Status: '' + LevelAndCategory: 1S + Name: kilomole + ConversionFactor: 10³ mol + Symbol: kmol + CommonCode: B45 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C34 +- Status: '' + LevelAndCategory: 1S + Name: kilomole per cubic metre + ConversionFactor: 10³ mol/m³ + Symbol: kmol/m³ + CommonCode: B46 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C36 + - M33 +- Status: '' + LevelAndCategory: 1S + Name: kilonewton + ConversionFactor: 10³ N + Symbol: kN + CommonCode: B47 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: kilonewton metre + ConversionFactor: 10³ N x m + Symbol: kN·m + CommonCode: B48 + Description: '' + conversion: + factor: 1000.0 + base_units: + - NU +- Status: '' + LevelAndCategory: 1S + Name: kiloohm + ConversionFactor: 10³ Ω + Symbol: kΩ + CommonCode: B49 + Description: '' + conversion: + factor: 1000.0 + base_units: + - OHM +- Status: X + LevelAndCategory: '3.9' + Name: billet + ConversionFactor: '' + Symbol: '' + CommonCode: B5 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kiloohm metre + ConversionFactor: 10³ Ω x m + Symbol: kΩ·m + CommonCode: B50 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C61 +- Status: D + LevelAndCategory: '2' + Name: kilopond + ConversionFactor: 9,806 65 N + Symbol: kp + CommonCode: B51 + Description: 'Synonym: kilogram-force' + conversion: + factor: 9.80665 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: kilosecond + ConversionFactor: 10³ s + Symbol: ks + CommonCode: B52 + Description: '' + conversion: + factor: 1000.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1S + Name: kilosiemens + ConversionFactor: 10³ S + Symbol: kS + CommonCode: B53 + Description: '' + conversion: + factor: 1000.0 + base_units: + - NQ +- Status: '' + LevelAndCategory: 1S + Name: kilosiemens per metre + ConversionFactor: 10³ S/m + Symbol: kS/m + CommonCode: B54 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1S + Name: kilovolt per metre + ConversionFactor: 10³ V/m + Symbol: kV/m + CommonCode: B55 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D50 +- Status: '' + LevelAndCategory: 1S + Name: kiloweber per metre + ConversionFactor: 10³ Wb/m + Symbol: kWb/m + CommonCode: B56 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D59 +- Status: '' + LevelAndCategory: '2' + Name: light year + ConversionFactor: 9,460 73 x 10¹⁵ m + Symbol: ly + CommonCode: B57 + Description: A unit of length defining the distance that light travels in a vacuum + in one year. + conversion: + factor: 9.46073e+15 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1M + Name: litre per mole + ConversionFactor: 10⁻³ m³/mol + Symbol: l/mol + CommonCode: B58 + Description: '' + conversion: + factor: 0.001 + base_units: + - A40 +- Status: '' + LevelAndCategory: 1S + Name: lumen hour + ConversionFactor: 3,6 x 10³ s x cd x sr + Symbol: lm·h + CommonCode: B59 + Description: '' + conversion: + factor: 3600.0 + base_units: + - B62 +- Status: X + LevelAndCategory: '3.9' + Name: bun + ConversionFactor: '' + Symbol: '' + CommonCode: B6 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: lumen per square metre + ConversionFactor: cd x sr/m² + Symbol: lm/m² + CommonCode: B60 + Description: '' + conversion: + factor: 1.0 + base_units: + - B60 +- Status: '' + LevelAndCategory: '1' + Name: lumen per watt + ConversionFactor: cd x sr/W + Symbol: lm/W + CommonCode: B61 + Description: '' + conversion: + factor: 1.0 + base_units: + - B61 +- Status: '' + LevelAndCategory: '1' + Name: lumen second + ConversionFactor: s x cd x sr + Symbol: lm·s + CommonCode: B62 + Description: '' + conversion: + factor: 1.0 + base_units: + - B62 +- Status: '' + LevelAndCategory: 1S + Name: lux hour + ConversionFactor: 3,6 x 10³ s x cd x sr / m² + Symbol: lx·h + CommonCode: B63 + Description: '' + conversion: + factor: 3600.0 + base_units: + - B64 +- Status: '' + LevelAndCategory: '1' + Name: lux second + ConversionFactor: s x cd x sr / m² + Symbol: lx·s + CommonCode: B64 + Description: '' + conversion: + factor: 1.0 + base_units: + - B64 +- Status: D + LevelAndCategory: '3.5' + Name: maxwell + ConversionFactor: 10⁻⁸ Wb + Symbol: Mx + CommonCode: B65 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - WEB +- Status: '' + LevelAndCategory: 1S + Name: megaampere per square metre + ConversionFactor: 10⁶ A/m² + Symbol: MA/m² + CommonCode: B66 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A41 +- Status: '' + LevelAndCategory: 1S + Name: megabecquerel per kilogram + ConversionFactor: 10⁶ Bq/kg + Symbol: MBq/kg + CommonCode: B67 + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.6' + Name: gigabit + ConversionFactor: '' + Symbol: Gbit + CommonCode: B68 + Description: A unit of information equal to 10⁹ bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: megacoulomb per cubic metre + ConversionFactor: 10⁶ C/m³ + Symbol: MC/m³ + CommonCode: B69 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A29 +- Status: '' + LevelAndCategory: '3.9' + Name: cycle + ConversionFactor: '' + Symbol: '' + CommonCode: B7 + Description: 'A unit of count defining the number of cycles (cycle: a recurrent + period of definite duration).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: megacoulomb per square metre + ConversionFactor: 10⁶ C/m² + Symbol: MC/m² + CommonCode: B70 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A34 +- Status: '' + LevelAndCategory: 1S + Name: megaelectronvolt + ConversionFactor: 10⁶ eV + Symbol: MeV + CommonCode: B71 + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: megagram per cubic metre + ConversionFactor: 10³ kg/m³ + Symbol: Mg/m³ + CommonCode: B72 + Description: '' + conversion: + factor: 1000.0 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1S + Name: meganewton + ConversionFactor: 10⁶ N + Symbol: MN + CommonCode: B73 + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: meganewton metre + ConversionFactor: 10⁶ N x m + Symbol: MN·m + CommonCode: B74 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - NU +- Status: '' + LevelAndCategory: 1S + Name: megaohm + ConversionFactor: 10⁶ Ω + Symbol: MΩ + CommonCode: B75 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - OHM +- Status: '' + LevelAndCategory: 1S + Name: megaohm metre + ConversionFactor: 10⁶ Ω x m + Symbol: MΩ·m + CommonCode: B76 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - C61 +- Status: '' + LevelAndCategory: 1S + Name: megasiemens per metre + ConversionFactor: 10⁶ S/m + Symbol: MS/m + CommonCode: B77 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1S + Name: megavolt + ConversionFactor: 10⁶ V + Symbol: MV + CommonCode: B78 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: 1S + Name: megavolt per metre + ConversionFactor: 10⁶ V/m + Symbol: MV/m + CommonCode: B79 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - D50 +- Status: '' + LevelAndCategory: '1' + Name: joule per cubic metre + ConversionFactor: J/m³ + Symbol: J/m³ + CommonCode: B8 + Description: '' + conversion: + factor: 1.0 + base_units: + - B8 +- Status: '' + LevelAndCategory: '3.6' + Name: gigabit per second + ConversionFactor: '' + Symbol: Gbit/s + CommonCode: B80 + Description: A unit of information equal to 10⁹ bits (binary digits) per second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal metre squared reciprocal second + ConversionFactor: m⁻²/s + Symbol: m⁻²/s + CommonCode: B81 + Description: '' + conversion: + factor: 1.0 + base_units: + - B81 +- Status: '' + LevelAndCategory: '3.1' + Name: inch per linear foot + ConversionFactor: '' + Symbol: '' + CommonCode: B82 + Description: A unit of length defining the number of inches per linear foot. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: metre to the fourth power + ConversionFactor: m⁴ + Symbol: m⁴ + CommonCode: B83 + Description: '' + conversion: + factor: 1.0 + base_units: + - B83 +- Status: '' + LevelAndCategory: 1S + Name: microampere + ConversionFactor: 10⁻⁶ A + Symbol: µA + CommonCode: B84 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - AMP +- Status: '' + LevelAndCategory: 1S + Name: microbar + ConversionFactor: 10⁻¹ Pa + Symbol: µbar + CommonCode: B85 + Description: '' + conversion: + factor: 0.1 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1S + Name: microcoulomb + ConversionFactor: 10⁻⁶ C + Symbol: µC + CommonCode: B86 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - A8 +- Status: '' + LevelAndCategory: 1S + Name: microcoulomb per cubic metre + ConversionFactor: 10⁻⁶ C/m³ + Symbol: µC/m³ + CommonCode: B87 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - A29 +- Status: '' + LevelAndCategory: 1S + Name: microcoulomb per square metre + ConversionFactor: 10⁻⁶ C/m² + Symbol: µC/m² + CommonCode: B88 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - A34 +- Status: '' + LevelAndCategory: 1S + Name: microfarad per metre + ConversionFactor: 10⁻⁶ F/m + Symbol: µF/m + CommonCode: B89 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: batt + ConversionFactor: '' + Symbol: '' + CommonCode: B9 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: microhenry + ConversionFactor: 10⁻⁶ H + Symbol: µH + CommonCode: B90 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - '81' +- Status: '' + LevelAndCategory: 1S + Name: microhenry per metre + ConversionFactor: 10⁻⁶ H/m + Symbol: µH/m + CommonCode: B91 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - A98 +- Status: '' + LevelAndCategory: 1S + Name: micronewton + ConversionFactor: 10⁻⁶ N + Symbol: µN + CommonCode: B92 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: micronewton metre + ConversionFactor: 10⁻⁶ N x m + Symbol: µN·m + CommonCode: B93 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - NU +- Status: '' + LevelAndCategory: 1S + Name: microohm + ConversionFactor: 10⁻⁶ Ω + Symbol: µΩ + CommonCode: B94 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - OHM +- Status: '' + LevelAndCategory: 1S + Name: microohm metre + ConversionFactor: 10⁻⁶ Ω x m + Symbol: µΩ·m + CommonCode: B95 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - C61 +- Status: '' + LevelAndCategory: 1S + Name: micropascal + ConversionFactor: 10⁻⁶ Pa + Symbol: µPa + CommonCode: B96 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1S + Name: microradian + ConversionFactor: 10⁻⁶ rad + Symbol: µrad + CommonCode: B97 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - C81 +- Status: '' + LevelAndCategory: 1S + Name: microsecond + ConversionFactor: 10⁻⁶ s + Symbol: µs + CommonCode: B98 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1S + Name: microsiemens + ConversionFactor: 10⁻⁶ S + Symbol: µS + CommonCode: B99 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - NQ +- Status: '' + LevelAndCategory: '1' + Name: bar [unit of pressure] + ConversionFactor: 10⁵ Pa + Symbol: bar + CommonCode: BAR + Description: '' + conversion: + factor: 100000.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.5' + Name: base box + ConversionFactor: '' + Symbol: '' + CommonCode: BB + Description: A unit of area of 112 sheets of tin mil products (tin plate, tin free + steel or black plate) 14 by 20 inches, or 31,360 square inches. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: board + ConversionFactor: '' + Symbol: '' + CommonCode: BD + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bundle + ConversionFactor: '' + Symbol: '' + CommonCode: BE + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: board foot + ConversionFactor: '' + Symbol: fbm + CommonCode: BFT + Description: 'A unit of volume defining the number of cords (cord: a stack of firewood + of 128 cubic feet).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bag + ConversionFactor: '' + Symbol: '' + CommonCode: BG + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: brush + ConversionFactor: '' + Symbol: '' + CommonCode: BH + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: brake horse power + ConversionFactor: 7,457 x 10² W + Symbol: '' + CommonCode: BHP + Description: '' + conversion: + factor: 745.7 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '3.7' + Name: billion (EUR) + ConversionFactor: 10¹² + Symbol: '' + CommonCode: BIL + Description: 'Synonym: trillion (US)' + conversion: + factor: 1000000000000.0 +- Status: X + LevelAndCategory: '3.3' + Name: bucket + ConversionFactor: '' + Symbol: '' + CommonCode: BJ + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: basket + ConversionFactor: '' + Symbol: '' + CommonCode: BK + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bale + ConversionFactor: '' + Symbol: '' + CommonCode: BL + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: dry barrel (US) + ConversionFactor: 1,156 27 x 10⁻¹ m³ + Symbol: bbl (US) + CommonCode: BLD + Description: '' + conversion: + factor: 0.115627 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: barrel (US) + ConversionFactor: 158,987 3 x 10⁻³ m³ + Symbol: barrel (US) + CommonCode: BLL + Description: '' + conversion: + factor: 0.1589873 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.3' + Name: bottle + ConversionFactor: '' + Symbol: '' + CommonCode: BO + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: hundred board foot + ConversionFactor: '' + Symbol: '' + CommonCode: BP + Description: A unit of volume equal to one hundred board foot. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: beats per minute + ConversionFactor: 1.667 x 10-2 /s + Symbol: '' + CommonCode: BPM + Description: The number of beats per minute. + conversion: + factor: 1.0 + base_units: + - BPM + - OPM +- Status: '' + LevelAndCategory: '1' + Name: becquerel + ConversionFactor: 27,027 x 10⁻¹² Ci + Symbol: Bq + CommonCode: BQL + Description: '' + conversion: + factor: 2.7027e-11 + base_units: [] +- Status: X + LevelAndCategory: '3.3' + Name: bar [unit of packaging] + ConversionFactor: '' + Symbol: '' + CommonCode: BR + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bolt + ConversionFactor: '' + Symbol: '' + CommonCode: BT + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) + ConversionFactor: 1,055 056 x 10³ J + Symbol: BtuIT + CommonCode: BTU + Description: '' + conversion: + factor: 1055.056 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: bushel (US) + ConversionFactor: 3,523 907 x 10⁻² m³ + Symbol: bu (US) + CommonCode: BUA + Description: '' + conversion: + factor: 0.03523907 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: bushel (UK) + ConversionFactor: 3,636 872 x 10⁻² m³ + Symbol: bushel (UK) + CommonCode: BUI + Description: '' + conversion: + factor: 0.03636872 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.9' + Name: base weight + ConversionFactor: '' + Symbol: '' + CommonCode: BW + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: box + ConversionFactor: '' + Symbol: '' + CommonCode: BX + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: million BTUs + ConversionFactor: '' + Symbol: '' + CommonCode: BZ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: call + ConversionFactor: '' + Symbol: '' + CommonCode: C0 + Description: 'A unit of count defining the number of calls (call: communication + session or visitation).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: composite product pound (total weight) + ConversionFactor: '' + Symbol: '' + CommonCode: C1 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: millifarad + ConversionFactor: 10⁻³ F + Symbol: mF + CommonCode: C10 + Description: '' + conversion: + factor: 0.001 + base_units: + - FAR +- Status: '' + LevelAndCategory: 1M + Name: milligal + ConversionFactor: 10⁻⁵ m/s² + Symbol: mGal + CommonCode: C11 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: 1S + Name: milligram per metre + ConversionFactor: 10⁻⁶ kg/m + Symbol: mg/m + CommonCode: C12 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - KL +- Status: '' + LevelAndCategory: 1S + Name: milligray + ConversionFactor: 10⁻³ Gy + Symbol: mGy + CommonCode: C13 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: millihenry + ConversionFactor: 10⁻³ H + Symbol: mH + CommonCode: C14 + Description: '' + conversion: + factor: 0.001 + base_units: + - '81' +- Status: '' + LevelAndCategory: 1S + Name: millijoule + ConversionFactor: 10⁻³ J + Symbol: mJ + CommonCode: C15 + Description: '' + conversion: + factor: 0.001 + base_units: + - JOU +- Status: '' + LevelAndCategory: 1S + Name: millimetre per second + ConversionFactor: 10⁻³ m/s + Symbol: mm/s + CommonCode: C16 + Description: '' + conversion: + factor: 0.001 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: 1S + Name: millimetre squared per second + ConversionFactor: 10⁻⁶ m²/s + Symbol: mm²/s + CommonCode: C17 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - S4 +- Status: '' + LevelAndCategory: 1S + Name: millimole + ConversionFactor: 10⁻³ mol + Symbol: mmol + CommonCode: C18 + Description: '' + conversion: + factor: 0.001 + base_units: + - C34 +- Status: '' + LevelAndCategory: '1' + Name: mole per kilogram + ConversionFactor: mol/kg + Symbol: mol/kg + CommonCode: C19 + Description: '' + conversion: + factor: 1.0 + base_units: + - C19 +- Status: X + LevelAndCategory: '3.5' + Name: carset + ConversionFactor: '' + Symbol: '' + CommonCode: C2 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: millinewton + ConversionFactor: 10⁻³ N + Symbol: mN + CommonCode: C20 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: '3.6' + Name: kibibit + ConversionFactor: '' + Symbol: Kibit + CommonCode: C21 + Description: A unit of information equal to 2¹⁰ (1024) bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: millinewton per metre + ConversionFactor: 10⁻³ N/m + Symbol: mN/m + CommonCode: C22 + Description: '' + conversion: + factor: 0.001 + base_units: + - 4P +- Status: '' + LevelAndCategory: 1S + Name: milliohm metre + ConversionFactor: 10⁻³ Ω x m + Symbol: mΩ·m + CommonCode: C23 + Description: '' + conversion: + factor: 0.001 + base_units: + - C61 +- Status: '' + LevelAndCategory: 1S + Name: millipascal second + ConversionFactor: 10⁻³ Pa x s + Symbol: mPa·s + CommonCode: C24 + Description: '' + conversion: + factor: 0.001 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: 1S + Name: milliradian + ConversionFactor: 10⁻³ rad + Symbol: mrad + CommonCode: C25 + Description: '' + conversion: + factor: 0.001 + base_units: + - C81 +- Status: '' + LevelAndCategory: 1S + Name: millisecond + ConversionFactor: 10⁻³ s + Symbol: ms + CommonCode: C26 + Description: '' + conversion: + factor: 0.001 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1S + Name: millisiemens + ConversionFactor: 10⁻³ S + Symbol: mS + CommonCode: C27 + Description: '' + conversion: + factor: 0.001 + base_units: + - NQ +- Status: '' + LevelAndCategory: 1S + Name: millisievert + ConversionFactor: 10⁻³ Sv + Symbol: mSv + CommonCode: C28 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: millitesla + ConversionFactor: 10⁻³ T + Symbol: mT + CommonCode: C29 + Description: '' + conversion: + factor: 0.001 + base_units: + - D33 +- Status: '' + LevelAndCategory: 1S + Name: microvolt per metre + ConversionFactor: 10⁻⁶ V/m + Symbol: µV/m + CommonCode: C3 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D50 +- Status: '' + LevelAndCategory: 1S + Name: millivolt per metre + ConversionFactor: 10⁻³ V/m + Symbol: mV/m + CommonCode: C30 + Description: '' + conversion: + factor: 0.001 + base_units: + - D50 +- Status: '' + LevelAndCategory: 1S + Name: milliwatt + ConversionFactor: 10⁻³ W + Symbol: mW + CommonCode: C31 + Description: '' + conversion: + factor: 0.001 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1S + Name: milliwatt per square metre + ConversionFactor: 10⁻³ W/m² + Symbol: mW/m² + CommonCode: C32 + Description: '' + conversion: + factor: 0.001 + base_units: + - D54 +- Status: '' + LevelAndCategory: 1S + Name: milliweber + ConversionFactor: 10⁻³ Wb + Symbol: mWb + CommonCode: C33 + Description: '' + conversion: + factor: 0.001 + base_units: + - WEB +- Status: '' + LevelAndCategory: '1' + Name: mole + ConversionFactor: mol + Symbol: mol + CommonCode: C34 + Description: '' + conversion: + factor: 1.0 + base_units: + - C34 +- Status: '' + LevelAndCategory: 1S + Name: mole per cubic decimetre + ConversionFactor: 10³ mol/m³ + Symbol: mol/dm³ + CommonCode: C35 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C36 + - M33 +- Status: '' + LevelAndCategory: '1' + Name: mole per cubic metre + ConversionFactor: mol/m³ + Symbol: mol/m³ + CommonCode: C36 + Description: '' + conversion: + factor: 1.0 + base_units: + - C36 + - M33 +- Status: '' + LevelAndCategory: '3.6' + Name: kilobit + ConversionFactor: '' + Symbol: kbit + CommonCode: C37 + Description: A unit of information equal to 10³ (1000) bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: mole per litre + ConversionFactor: 10³ mol/m³ + Symbol: mol/l + CommonCode: C38 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C36 + - M33 +- Status: '' + LevelAndCategory: 1S + Name: nanoampere + ConversionFactor: 10⁻⁹ A + Symbol: nA + CommonCode: C39 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: X + LevelAndCategory: '3.5' + Name: carload + ConversionFactor: '' + Symbol: '' + CommonCode: C4 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: nanocoulomb + ConversionFactor: 10⁻⁹ C + Symbol: nC + CommonCode: C40 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanofarad + ConversionFactor: 10⁻⁹ F + Symbol: nF + CommonCode: C41 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanofarad per metre + ConversionFactor: 10⁻⁹ F/m + Symbol: nF/m + CommonCode: C42 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanohenry + ConversionFactor: 10⁻⁹ H + Symbol: nH + CommonCode: C43 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanohenry per metre + ConversionFactor: 10⁻⁹ H/m + Symbol: nH/m + CommonCode: C44 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanometre + ConversionFactor: 10⁻⁹ m + Symbol: nm + CommonCode: C45 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanoohm metre + ConversionFactor: 10⁻⁹ Ω·x m + Symbol: nΩ·m + CommonCode: C46 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanosecond + ConversionFactor: 10⁻⁹ s + Symbol: ns + CommonCode: C47 + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1S + Name: nanotesla + ConversionFactor: 10⁻⁹ T + Symbol: nT + CommonCode: C48 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanowatt + ConversionFactor: 10⁻⁹ W + Symbol: nW + CommonCode: C49 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: cost + ConversionFactor: '' + Symbol: '' + CommonCode: C5 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: neper + ConversionFactor: Np + Symbol: Np + CommonCode: C50 + Description: '' + conversion: + factor: 1.0 + base_units: + - C50 +- Status: '' + LevelAndCategory: '1' + Name: neper per second + ConversionFactor: Np/s + Symbol: Np/s + CommonCode: C51 + Description: '' + conversion: + factor: 1.0 + base_units: + - C51 +- Status: '' + LevelAndCategory: 1S + Name: picometre + ConversionFactor: 10⁻¹² m + Symbol: pm + CommonCode: C52 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - MTR +- Status: '' + LevelAndCategory: '1' + Name: newton metre second + ConversionFactor: N x m x s + Symbol: N·m·s + CommonCode: C53 + Description: '' + conversion: + factor: 1.0 + base_units: + - C53 +- Status: '' + LevelAndCategory: '1' + Name: newton metre squared per kilogram squared + ConversionFactor: N x m²/kg² + Symbol: N·m²/kg² + CommonCode: C54 + Description: '' + conversion: + factor: 1.0 + base_units: + - C54 +- Status: '' + LevelAndCategory: 1S + Name: newton per square metre + ConversionFactor: Pa + Symbol: N/m² + CommonCode: C55 + Description: '' + conversion: + factor: 1.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1S + Name: newton per square millimetre + ConversionFactor: 10⁶ Pa + Symbol: N/mm² + CommonCode: C56 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '1' + Name: newton second + ConversionFactor: N x s + Symbol: N·s + CommonCode: C57 + Description: '' + conversion: + factor: 1.0 + base_units: + - C57 +- Status: '' + LevelAndCategory: '1' + Name: newton second per metre + ConversionFactor: N x s/m + Symbol: N·s/m + CommonCode: C58 + Description: '' + conversion: + factor: 1.0 + base_units: + - C58 +- Status: '' + LevelAndCategory: '1' + Name: octave + ConversionFactor: '' + Symbol: '' + CommonCode: C59 + Description: A unit used in music to describe the ratio in frequency between notes. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: cell + ConversionFactor: '' + Symbol: '' + CommonCode: C6 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: ohm centimetre + ConversionFactor: '10⁻² Ω x m ' + Symbol: Ω·cm + CommonCode: C60 + Description: '' + conversion: + factor: 0.01 + base_units: + - C61 +- Status: '' + LevelAndCategory: '1' + Name: ohm metre + ConversionFactor: Ω x m + Symbol: Ω·m + CommonCode: C61 + Description: '' + conversion: + factor: 1.0 + base_units: + - C61 +- Status: '' + LevelAndCategory: '1' + Name: one + ConversionFactor: '1' + Symbol: '1' + CommonCode: C62 + Description: 'Synonym: unit' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: parsec + ConversionFactor: 3,085 678 x 10¹⁶ m + Symbol: pc + CommonCode: C63 + Description: '' + conversion: + factor: 3.085678e+16 + base_units: + - MTR +- Status: '' + LevelAndCategory: '1' + Name: pascal per kelvin + ConversionFactor: Pa/K + Symbol: Pa/K + CommonCode: C64 + Description: '' + conversion: + factor: 1.0 + base_units: + - C64 +- Status: '' + LevelAndCategory: '1' + Name: pascal second + ConversionFactor: Pa x s + Symbol: Pa·s + CommonCode: C65 + Description: '' + conversion: + factor: 1.0 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '1' + Name: pascal second per cubic metre + ConversionFactor: Pa x s/m³ + Symbol: Pa·s/m³ + CommonCode: C66 + Description: '' + conversion: + factor: 1.0 + base_units: + - C66 +- Status: '' + LevelAndCategory: '1' + Name: pascal second per metre + ConversionFactor: Pa x s/m + Symbol: Pa· s/m + CommonCode: C67 + Description: '' + conversion: + factor: 1.0 + base_units: + - C67 +- Status: '' + LevelAndCategory: 1S + Name: petajoule + ConversionFactor: 10¹⁵ J + Symbol: PJ + CommonCode: C68 + Description: '' + conversion: + factor: 1.0e+15 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: phon + ConversionFactor: '' + Symbol: '' + CommonCode: C69 + Description: A unit of subjective sound loudness. A sound has loudness p phons if + it seems to the listener to be equal in loudness to the sound of a pure tone of + frequency 1 kilohertz and strength p decibels. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: centipoise + ConversionFactor: 10⁻³ Pa x s + Symbol: cP + CommonCode: C7 + Description: '' + conversion: + factor: 0.001 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: 1S + Name: picoampere + ConversionFactor: 10⁻¹² A + Symbol: pA + CommonCode: C70 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - AMP +- Status: '' + LevelAndCategory: 1S + Name: picocoulomb + ConversionFactor: 10⁻¹² C + Symbol: pC + CommonCode: C71 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - A8 +- Status: '' + LevelAndCategory: 1S + Name: picofarad per metre + ConversionFactor: 10⁻¹² F/m + Symbol: pF/m + CommonCode: C72 + Description: '' + conversion: + factor: 1.0e-12 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: picohenry + ConversionFactor: 10⁻¹² H + Symbol: pH + CommonCode: C73 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - '81' +- Status: '' + LevelAndCategory: '3.6' + Name: kilobit per second + ConversionFactor: 10³ bit/s + Symbol: kbit/s + CommonCode: C74 + Description: A unit of information equal to 10³ (1000) bits (binary digits) per + second. + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: picowatt + ConversionFactor: 10⁻¹² W + Symbol: pW + CommonCode: C75 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1S + Name: picowatt per square metre + ConversionFactor: 10⁻¹² W/m² + Symbol: pW/m² + CommonCode: C76 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - D54 +- Status: X + LevelAndCategory: '3.1' + Name: pound gage + ConversionFactor: '' + Symbol: '' + CommonCode: C77 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: pound-force + ConversionFactor: 4,448 222 N + Symbol: lbf + CommonCode: C78 + Description: '' + conversion: + factor: 4.448222 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: kilovolt ampere hour + ConversionFactor: '' + Symbol: kVAh + CommonCode: C79 + Description: A unit of accumulated energy of 1000 volt amperes over a period of + one hour. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: millicoulomb per kilogram + ConversionFactor: 10⁻³ C/kg + Symbol: mC/kg + CommonCode: C8 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: rad + ConversionFactor: 10⁻² Gy + Symbol: rad + CommonCode: C80 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: radian + ConversionFactor: rad + Symbol: rad + CommonCode: C81 + Description: '' + conversion: + factor: 1.0 + base_units: + - C81 +- Status: '' + LevelAndCategory: '1' + Name: radian square metre per mole + ConversionFactor: rad x m²/mol + Symbol: rad·m²/mol + CommonCode: C82 + Description: '' + conversion: + factor: 1.0 + base_units: + - C82 +- Status: '' + LevelAndCategory: '1' + Name: radian square metre per kilogram + ConversionFactor: rad x m²/kg + Symbol: rad·m²/kg + CommonCode: C83 + Description: '' + conversion: + factor: 1.0 + base_units: + - C83 +- Status: '' + LevelAndCategory: '1' + Name: radian per metre + ConversionFactor: rad/m + Symbol: rad/m + CommonCode: C84 + Description: '' + conversion: + factor: 1.0 + base_units: + - C84 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal angstrom + ConversionFactor: 10¹⁰ m⁻¹ + Symbol: Å⁻¹ + CommonCode: C85 + Description: '' + conversion: + factor: 10000000000.0 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal cubic metre + ConversionFactor: m⁻³ + Symbol: m⁻³ + CommonCode: C86 + Description: '' + conversion: + factor: 1.0 + base_units: + - C86 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal cubic metre per second + ConversionFactor: m⁻³/s + Symbol: m⁻³/s + CommonCode: C87 + Description: 'Synonym: reciprocal second per cubic metre' + conversion: + factor: 1.0 + base_units: + - C87 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal electron volt per cubic metre + ConversionFactor: 6,241 46 x 10¹⁸ J⁻¹/m³ + Symbol: eV⁻¹/m³ + CommonCode: C88 + Description: '' + conversion: + factor: 6.24146e+18 + base_units: + - C90 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal henry + ConversionFactor: H⁻¹ + Symbol: H⁻¹ + CommonCode: C89 + Description: '' + conversion: + factor: 1.0 + base_units: + - C89 +- Status: '' + LevelAndCategory: '3.9' + Name: coil group + ConversionFactor: '' + Symbol: '' + CommonCode: C9 + Description: 'A unit of count defining the number of coil groups (coil group: groups + of items arranged by lengths of those items placed in a joined sequence of concentric + circles).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal joule per cubic metre + ConversionFactor: J⁻¹/m³ + Symbol: J⁻¹/m³ + CommonCode: C90 + Description: '' + conversion: + factor: 1.0 + base_units: + - C90 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal kelvin or kelvin to the power minus one + ConversionFactor: K⁻¹ + Symbol: K⁻¹ + CommonCode: C91 + Description: '' + conversion: + factor: 1.0 + base_units: + - C91 + - N83 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal metre + ConversionFactor: m⁻¹ + Symbol: m⁻¹ + CommonCode: C92 + Description: '' + conversion: + factor: 1.0 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal square metre + ConversionFactor: m⁻² + Symbol: m⁻² + CommonCode: C93 + Description: 'Synonym: reciprocal metre squared' + conversion: + factor: 1.0 + base_units: + - C93 +- Status: '' + LevelAndCategory: 1S + Name: reciprocal minute + ConversionFactor: 1,666 667 x 10⁻² s + Symbol: min⁻¹ + CommonCode: C94 + Description: '' + conversion: + factor: 0.01666667 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '1' + Name: reciprocal mole + ConversionFactor: mol⁻¹ + Symbol: mol⁻¹ + CommonCode: C95 + Description: '' + conversion: + factor: 1.0 + base_units: + - C95 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal pascal or pascal to the power minus one + ConversionFactor: Pa⁻¹ + Symbol: Pa⁻¹ + CommonCode: C96 + Description: '' + conversion: + factor: 1.0 + base_units: + - C96 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal second + ConversionFactor: s⁻¹ + Symbol: s⁻¹ + CommonCode: C97 + Description: '' + conversion: + factor: 1.0 + base_units: + - C97 +- Status: X + LevelAndCategory: '1' + Name: reciprocal second per cubic metre + ConversionFactor: s⁻¹/m³ + Symbol: s⁻¹/m³ + CommonCode: C98 + Description: '' + conversion: + factor: 1.0 + base_units: + - C98 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal second per metre squared + ConversionFactor: s⁻¹/m² + Symbol: s⁻¹/m² + CommonCode: C99 + Description: '' + conversion: + factor: 1.0 + base_units: + - C99 +- Status: X + LevelAndCategory: '3.3' + Name: can + ConversionFactor: '' + Symbol: '' + CommonCode: CA + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: carrying capacity in metric ton + ConversionFactor: '' + Symbol: '' + CommonCode: CCT + Description: A unit of mass defining the carrying capacity, expressed as the number + of metric tons. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: candela + ConversionFactor: cd + Symbol: cd + CommonCode: CDL + Description: '' + conversion: + factor: 1.0 + base_units: + - CDL +- Status: '' + LevelAndCategory: '1' + Name: degree Celsius + ConversionFactor: 1 x K + Symbol: "°C" + CommonCode: CEL + Description: 'Refer ISO 80000-5 (Quantities and units — Part 5: Thermodynamics)' + conversion: + factor: 1.0 + base_units: + - CEL + - KEL +- Status: '' + LevelAndCategory: '3.7' + Name: hundred + ConversionFactor: '100' + Symbol: '' + CommonCode: CEN + Description: A unit of count defining the number of units in multiples of 100. + conversion: + factor: 100.0 +- Status: '' + LevelAndCategory: '3.9' + Name: card + ConversionFactor: '' + Symbol: '' + CommonCode: CG + Description: 'A unit of count defining the number of units of card (card: thick + stiff paper or cardboard).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: centigram + ConversionFactor: 10⁻⁵ kg + Symbol: cg + CommonCode: CGM + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.4' + Name: container + ConversionFactor: '' + Symbol: '' + CommonCode: CH + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: cone + ConversionFactor: '' + Symbol: '' + CommonCode: CJ + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: connector + ConversionFactor: '' + Symbol: '' + CommonCode: CK + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: coulomb per kilogram + ConversionFactor: A x s/kg + Symbol: C/kg + CommonCode: CKG + Description: '' + conversion: + factor: 1.0 + base_units: + - CKG +- Status: X + LevelAndCategory: '3.3' + Name: coil + ConversionFactor: '' + Symbol: '' + CommonCode: CL + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: hundred leave + ConversionFactor: '' + Symbol: '' + CommonCode: CLF + Description: A unit of count defining the number of leaves, expressed in units of + one hundred leaves. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: centilitre + ConversionFactor: 10⁻⁵ m³ + Symbol: cl + CommonCode: CLT + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: square centimetre + ConversionFactor: 10⁻⁴ m² + Symbol: cm² + CommonCode: CMK + Description: '' + conversion: + factor: 0.0001 + base_units: + - MTK +- Status: '' + LevelAndCategory: 1S + Name: cubic centimetre + ConversionFactor: 10⁻⁶ m³ + Symbol: cm³ + CommonCode: CMQ + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S,3.5 + Name: centimetre + ConversionFactor: 10⁻² m + Symbol: cm + CommonCode: CMT + Description: '' + conversion: + factor: 0.01 + base_units: + - MTR +- Status: '' + LevelAndCategory: 3.2,3.8 + Name: hundred pack + ConversionFactor: '' + Symbol: '' + CommonCode: CNP + Description: 'A unit of count defining the number of hundred-packs (hundred-pack: + set of one hundred items packaged together).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: cental (UK) + ConversionFactor: 45,359 237 kg + Symbol: '' + CommonCode: CNT + Description: A unit of mass equal to one hundred weight (US). + conversion: + factor: 45.359237 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.3' + Name: carboy + ConversionFactor: '' + Symbol: '' + CommonCode: CO + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: coulomb + ConversionFactor: A x s + Symbol: C + CommonCode: COU + Description: '' + conversion: + factor: 1.0 + base_units: + - COU +- Status: X + LevelAndCategory: '3.9' + Name: cartridge + ConversionFactor: '' + Symbol: '' + CommonCode: CQ + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: crate + ConversionFactor: '' + Symbol: '' + CommonCode: CR + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: case + ConversionFactor: '' + Symbol: '' + CommonCode: CS + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: carton + ConversionFactor: '' + Symbol: '' + CommonCode: CT + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: content gram + ConversionFactor: '' + Symbol: '' + CommonCode: CTG + Description: A unit of mass defining the number of grams of a named item in a product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: metric carat + ConversionFactor: 200 mg + Symbol: '' + CommonCode: CTM + Description: '' + conversion: + factor: 200.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: content ton (metric) + ConversionFactor: '' + Symbol: '' + CommonCode: CTN + Description: A unit of mass defining the number of metric tons of a named item in + a product. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: cup + ConversionFactor: '' + Symbol: '' + CommonCode: CU + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: curie + ConversionFactor: 3,7 x 10¹⁰ Bq + Symbol: Ci + CommonCode: CUR + Description: '' + conversion: + factor: 37000000000.0 + base_units: [] +- Status: X + LevelAndCategory: '3.3' + Name: cover + ConversionFactor: '' + Symbol: '' + CommonCode: CV + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: hundred pound (cwt) / hundred weight (US) + ConversionFactor: 45,359 2 kg + Symbol: cwt (US) + CommonCode: CWA + Description: '' + conversion: + factor: 45.3592 + base_units: + - KGM +- Status: '' + LevelAndCategory: '2' + Name: hundred weight (UK) + ConversionFactor: 50,802 35 kg + Symbol: cwt (UK) + CommonCode: CWI + Description: '' + conversion: + factor: 50.80235 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.3' + Name: cylinder + ConversionFactor: '' + Symbol: '' + CommonCode: CY + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: combo + ConversionFactor: '' + Symbol: '' + CommonCode: CZ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilowatt hour per hour + ConversionFactor: '' + Symbol: kW·h/h + CommonCode: D03 + Description: A unit of accumulated energy of a thousand watts over a period of one + hour. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.2' + Name: lot [unit of weight] + ConversionFactor: '' + Symbol: '' + CommonCode: D04 + Description: A unit of weight equal to about 1/2 ounce or 15 grams. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal second per steradian + ConversionFactor: s⁻¹/sr + Symbol: s⁻¹/sr + CommonCode: D1 + Description: '' + conversion: + factor: 1.0 + base_units: + - D1 +- Status: '' + LevelAndCategory: '1' + Name: siemens per metre + ConversionFactor: S/m + Symbol: S/m + CommonCode: D10 + Description: '' + conversion: + factor: 1.0 + base_units: + - D10 +- Status: '' + LevelAndCategory: '3.6' + Name: mebibit + ConversionFactor: '' + Symbol: Mibit + CommonCode: D11 + Description: A unit of information equal to 2²⁰ (1048576) bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: siemens square metre per mole + ConversionFactor: S x m²/mol + Symbol: S·m²/mol + CommonCode: D12 + Description: '' + conversion: + factor: 1.0 + base_units: + - D12 +- Status: '' + LevelAndCategory: '1' + Name: sievert + ConversionFactor: m²/s² + Symbol: Sv + CommonCode: D13 + Description: '' + conversion: + factor: 1.0 + base_units: + - A95 + - D13 +- Status: X + LevelAndCategory: '3.8' + Name: thousand linear yard + ConversionFactor: '' + Symbol: '' + CommonCode: D14 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: sone + ConversionFactor: '' + Symbol: '' + CommonCode: D15 + Description: A unit of subjective sound loudness. One sone is the loudness of a + pure tone of frequency one kilohertz and strength 40 decibels. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: square centimetre per erg + ConversionFactor: 10³ m²/J + Symbol: cm²/erg + CommonCode: D16 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D20 +- Status: '' + LevelAndCategory: '2' + Name: square centimetre per steradian erg + ConversionFactor: 10³ m²/(sr x J) + Symbol: cm²/(sr·erg) + CommonCode: D17 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D25 +- Status: '' + LevelAndCategory: '1' + Name: metre kelvin + ConversionFactor: m x K + Symbol: m·K + CommonCode: D18 + Description: '' + conversion: + factor: 1.0 + base_units: + - D18 +- Status: '' + LevelAndCategory: '1' + Name: square metre kelvin per watt + ConversionFactor: m² x K/W + Symbol: m²·K/W + CommonCode: D19 + Description: '' + conversion: + factor: 1.0 + base_units: + - D19 +- Status: '' + LevelAndCategory: '1' + Name: reciprocal second per steradian metre squared + ConversionFactor: s⁻¹/(sr x m²) + Symbol: s⁻¹/(sr·m²) + CommonCode: D2 + Description: '' + conversion: + factor: 1.0 + base_units: + - D2 +- Status: '' + LevelAndCategory: '1' + Name: square metre per joule + ConversionFactor: m²/J + Symbol: m²/J + CommonCode: D20 + Description: '' + conversion: + factor: 1.0 + base_units: + - D20 +- Status: '' + LevelAndCategory: '1' + Name: square metre per kilogram + ConversionFactor: m²/kg + Symbol: m²/kg + CommonCode: D21 + Description: '' + conversion: + factor: 1.0 + base_units: + - D21 +- Status: '' + LevelAndCategory: '1' + Name: square metre per mole + ConversionFactor: m²/mol + Symbol: m²/mol + CommonCode: D22 + Description: '' + conversion: + factor: 1.0 + base_units: + - D22 +- Status: '' + LevelAndCategory: '3.9' + Name: pen gram (protein) + ConversionFactor: '' + Symbol: '' + CommonCode: D23 + Description: A unit of count defining the number of grams of amino acid prescribed + for parenteral/enteral therapy. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: square metre per steradian + ConversionFactor: m²/sr + Symbol: m²/sr + CommonCode: D24 + Description: '' + conversion: + factor: 1.0 + base_units: + - D24 +- Status: '' + LevelAndCategory: '1' + Name: square metre per steradian joule + ConversionFactor: m²/(sr x J) + Symbol: m²/(sr·J) + CommonCode: D25 + Description: '' + conversion: + factor: 1.0 + base_units: + - D25 +- Status: '' + LevelAndCategory: '1' + Name: square metre per volt second + ConversionFactor: m²/(V x s) + Symbol: m²/(V·s) + CommonCode: D26 + Description: '' + conversion: + factor: 1.0 + base_units: + - D26 +- Status: '' + LevelAndCategory: '1' + Name: steradian + ConversionFactor: sr + Symbol: sr + CommonCode: D27 + Description: '' + conversion: + factor: 1.0 + base_units: + - D27 +- Status: X + LevelAndCategory: '3.9' + Name: syphon + ConversionFactor: '' + Symbol: '' + CommonCode: D28 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: terahertz + ConversionFactor: 10¹² Hz + Symbol: THz + CommonCode: D29 + Description: '' + conversion: + factor: 1000000000000.0 + base_units: + - HTZ +- Status: '' + LevelAndCategory: 1S + Name: terajoule + ConversionFactor: 10¹² J + Symbol: TJ + CommonCode: D30 + Description: '' + conversion: + factor: 1000000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: terawatt + ConversionFactor: 10¹² W + Symbol: TW + CommonCode: D31 + Description: '' + conversion: + factor: 1000000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: terawatt hour + ConversionFactor: 3,6 x 10¹⁵ J + Symbol: TW·h + CommonCode: D32 + Description: '' + conversion: + factor: 3.6e+15 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: tesla + ConversionFactor: T + Symbol: T + CommonCode: D33 + Description: '' + conversion: + factor: 1.0 + base_units: + - D33 +- Status: '' + LevelAndCategory: '3.5' + Name: tex + ConversionFactor: 10⁻⁶ kg/m + Symbol: tex (g/km) + CommonCode: D34 + Description: A unit of yarn density. One decitex equals a mass of 1 gram per 1 kilometre + of length. + conversion: + factor: 1.0e-06 + base_units: + - KL +- Status: D + LevelAndCategory: '2' + Name: calorie (thermochemical) + ConversionFactor: 4,184 J + Symbol: calth + CommonCode: D35 + Description: '' + conversion: + factor: 4.184 + base_units: + - JOU +- Status: '' + LevelAndCategory: '3.6' + Name: megabit + ConversionFactor: '' + Symbol: Mbit + CommonCode: D36 + Description: A unit of information equal to 10⁶ (1000000) bits (binary digits). + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: calorie (thermochemical) per gram kelvin + ConversionFactor: 4,184 x 10³ J/(kg x K) + Symbol: calth/(g·K) + CommonCode: D37 + Description: '' + conversion: + factor: 4184.0 + base_units: + - B11 +- Status: D + LevelAndCategory: '2' + Name: calorie (thermochemical) per second centimetre kelvin + ConversionFactor: 418,4 W/(m x K) + Symbol: calth/(s·cm·K) + CommonCode: D38 + Description: '' + conversion: + factor: 418.4 + base_units: + - D53 + - N80 +- Status: D + LevelAndCategory: '2' + Name: calorie (thermochemical) per second square centimetre kelvin + ConversionFactor: 4,184 x10⁴ W/(m² x K) + Symbol: calth/(s·cm²·K) + CommonCode: D39 + Description: '' + conversion: + factor: 4.184 + base_units: [] +- Status: X + LevelAndCategory: '3.8' + Name: thousand litre + ConversionFactor: m³ + Symbol: '' + CommonCode: D40 + Description: '' + conversion: + factor: 1.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: tonne per cubic metre + ConversionFactor: 10³ kg/m³ + Symbol: t/m³ + CommonCode: D41 + Description: '' + conversion: + factor: 1000.0 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: tropical year + ConversionFactor: 3,155 692 5 x 10⁷ s + Symbol: y (tropical) + CommonCode: D42 + Description: '' + conversion: + factor: 3.155692 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: unified atomic mass unit + ConversionFactor: 1,660 538 782 x 10⁻²⁷ kg + Symbol: u + CommonCode: D43 + Description: '' + conversion: + factor: 1.66 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: var + ConversionFactor: V x A + Symbol: var + CommonCode: D44 + Description: The name of the unit is an acronym for volt-ampere-reactive. + conversion: + factor: 1.0 + base_units: + - D44 +- Status: '' + LevelAndCategory: '1' + Name: volt squared per kelvin squared + ConversionFactor: V²/K² + Symbol: V²/K² + CommonCode: D45 + Description: '' + conversion: + factor: 1.0 + base_units: + - D45 +- Status: '' + LevelAndCategory: '1' + Name: volt - ampere + ConversionFactor: W + Symbol: V·A + CommonCode: D46 + Description: '' + conversion: + factor: 1.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1S + Name: volt per centimetre + ConversionFactor: V/m x 10² + Symbol: V/cm + CommonCode: D47 + Description: '' + conversion: + factor: 1.0 + base_units: + - D47 +- Status: '' + LevelAndCategory: '1' + Name: volt per kelvin + ConversionFactor: V/K + Symbol: V/K + CommonCode: D48 + Description: '' + conversion: + factor: 1.0 + base_units: + - D48 +- Status: '' + LevelAndCategory: 1S + Name: millivolt per kelvin + ConversionFactor: 10⁻³ V/K + Symbol: mV/K + CommonCode: D49 + Description: '' + conversion: + factor: 0.001 + base_units: + - D48 +- Status: '' + LevelAndCategory: '2' + Name: kilogram per square centimetre + ConversionFactor: 10⁴ kg/m² + Symbol: kg/cm² + CommonCode: D5 + Description: '' + conversion: + factor: 10000.0 + base_units: + - '28' +- Status: '' + LevelAndCategory: '1' + Name: volt per metre + ConversionFactor: V/m + Symbol: V/m + CommonCode: D50 + Description: '' + conversion: + factor: 1.0 + base_units: + - D50 +- Status: '' + LevelAndCategory: 1S + Name: volt per millimetre + ConversionFactor: 10³ V/m + Symbol: V/mm + CommonCode: D51 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D50 +- Status: '' + LevelAndCategory: '1' + Name: watt per kelvin + ConversionFactor: W/K + Symbol: W/K + CommonCode: D52 + Description: '' + conversion: + factor: 1.0 + base_units: + - D52 +- Status: '' + LevelAndCategory: '1' + Name: watt per metre kelvin + ConversionFactor: W/(m x K) + Symbol: W/(m·K) + CommonCode: D53 + Description: '' + conversion: + factor: 1.0 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '1' + Name: watt per square metre + ConversionFactor: W/m² + Symbol: W/m² + CommonCode: D54 + Description: '' + conversion: + factor: 1.0 + base_units: + - D54 +- Status: '' + LevelAndCategory: '1' + Name: watt per square metre kelvin + ConversionFactor: W/(m² x K) + Symbol: W/(m²·K) + CommonCode: D55 + Description: '' + conversion: + factor: 1.0 + base_units: + - D55 +- Status: '' + LevelAndCategory: '1' + Name: watt per square metre kelvin to the fourth power + ConversionFactor: W/(m² x K⁴) + Symbol: W/(m²·K⁴) + CommonCode: D56 + Description: '' + conversion: + factor: 1.0 + base_units: + - D56 +- Status: '' + LevelAndCategory: '1' + Name: watt per steradian + ConversionFactor: W/sr + Symbol: W/sr + CommonCode: D57 + Description: '' + conversion: + factor: 1.0 + base_units: + - D57 +- Status: '' + LevelAndCategory: '1' + Name: watt per steradian square metre + ConversionFactor: W/(sr x m²) + Symbol: W/(sr·m²) + CommonCode: D58 + Description: '' + conversion: + factor: 1.0 + base_units: + - D58 +- Status: '' + LevelAndCategory: '1' + Name: weber per metre + ConversionFactor: Wb/m + Symbol: Wb/m + CommonCode: D59 + Description: '' + conversion: + factor: 1.0 + base_units: + - D59 +- Status: '' + LevelAndCategory: '2' + Name: roentgen per second + ConversionFactor: 2,58 x 10⁻⁴ C/(kg x s) + Symbol: R/s + CommonCode: D6 + Description: '' + conversion: + factor: 0.000258 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: weber per millimetre + ConversionFactor: 10³ Wb/m + Symbol: Wb/mm + CommonCode: D60 + Description: '' + conversion: + factor: 1000.0 + base_units: + - D59 +- Status: '' + LevelAndCategory: '1' + Name: minute [unit of angle] + ConversionFactor: 2,908 882 x 10⁻⁴ rad + Symbol: "'" + CommonCode: D61 + Description: '' + conversion: + factor: 0.0002908882 + base_units: + - C81 +- Status: '' + LevelAndCategory: '1' + Name: second [unit of angle] + ConversionFactor: 4,848 137 x 10⁻⁶ rad + Symbol: "\"" + CommonCode: D62 + Description: '' + conversion: + factor: 4.848137e-06 + base_units: + - C81 +- Status: '' + LevelAndCategory: '3.9' + Name: book + ConversionFactor: '' + Symbol: '' + CommonCode: D63 + Description: 'A unit of count defining the number of books (book: set of items bound + together or written document of a material whole).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: block + ConversionFactor: '' + Symbol: '' + CommonCode: D64 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: round + ConversionFactor: '' + Symbol: '' + CommonCode: D65 + Description: 'A unit of count defining the number of rounds (round: A circular or + cylindrical object).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: cassette + ConversionFactor: '' + Symbol: '' + CommonCode: D66 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: dollar per hour + ConversionFactor: '' + Symbol: '' + CommonCode: D67 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: number of words + ConversionFactor: '' + Symbol: '' + CommonCode: D68 + Description: A unit of count defining the number of words. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: inch to the fourth power + ConversionFactor: 41,623 14 x 10⁻⁸ m⁴ + Symbol: in⁴ + CommonCode: D69 + Description: '' + conversion: + factor: 4.162314e-07 + base_units: + - B83 +- Status: X + LevelAndCategory: '3.9' + Name: sandwich + ConversionFactor: '' + Symbol: '' + CommonCode: D7 + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: calorie (international table) + ConversionFactor: 4,186 8 J + Symbol: calIT + CommonCode: D70 + Description: '' + conversion: + factor: 4.1868 + base_units: + - JOU +- Status: D + LevelAndCategory: '2' + Name: calorie (international table) per second centimetre kelvin + ConversionFactor: 418,68 W/(m x K) + Symbol: calIT/(s·cm·K) + CommonCode: D71 + Description: '' + conversion: + factor: 418.68 + base_units: + - D53 + - N80 +- Status: D + LevelAndCategory: '2' + Name: calorie (international table) per second square centimetre kelvin + ConversionFactor: 4,186 8 x 10⁴ W/(m² x K) + Symbol: calIT/(s·cm²·K) + CommonCode: D72 + Description: '' + conversion: + factor: 41868.0 + base_units: + - D55 +- Status: '' + LevelAndCategory: '1' + Name: joule square metre + ConversionFactor: J x m² + Symbol: J·m² + CommonCode: D73 + Description: '' + conversion: + factor: 1.0 + base_units: + - D73 +- Status: '' + LevelAndCategory: '1' + Name: kilogram per mole + ConversionFactor: kg/mol + Symbol: kg/mol + CommonCode: D74 + Description: '' + conversion: + factor: 1.0 + base_units: + - D74 +- Status: D + LevelAndCategory: '2' + Name: calorie (international table) per gram + ConversionFactor: 4 186,8 J/kg + Symbol: calIT/g + CommonCode: D75 + Description: '' + conversion: + factor: 4186.8 + base_units: + - J2 +- Status: D + LevelAndCategory: '2' + Name: calorie (international table) per gram kelvin + ConversionFactor: 4 186,8 J/(kg x K) + Symbol: calIT/(g·K) + CommonCode: D76 + Description: '' + conversion: + factor: 4186.8 + base_units: + - B11 +- Status: '' + LevelAndCategory: 1S + Name: megacoulomb + ConversionFactor: 10⁶ C + Symbol: MC + CommonCode: D77 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - A8 +- Status: '' + LevelAndCategory: '3.1' + Name: megajoule per second + ConversionFactor: '' + Symbol: MJ/s + CommonCode: D78 + Description: A unit of accumulated energy equal to one million joules per second. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: beam + ConversionFactor: '' + Symbol: '' + CommonCode: D79 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.7' + Name: draize score + ConversionFactor: '' + Symbol: '' + CommonCode: D8 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: microwatt + ConversionFactor: 10⁻⁶ W + Symbol: µW + CommonCode: D80 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1S + Name: microtesla + ConversionFactor: 10⁻⁶ T + Symbol: µT + CommonCode: D81 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D33 +- Status: '' + LevelAndCategory: 1S + Name: microvolt + ConversionFactor: 10⁻⁶ V + Symbol: µV + CommonCode: D82 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: 1S + Name: millinewton metre + ConversionFactor: 10⁻³ N x m + Symbol: mN·m + CommonCode: D83 + Description: '' + conversion: + factor: 0.001 + base_units: + - NU +- Status: '' + LevelAndCategory: 1S + Name: microwatt per square metre + ConversionFactor: 10⁻⁶ W/m² + Symbol: µW/m² + CommonCode: D85 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D54 +- Status: '' + LevelAndCategory: 1S + Name: millicoulomb + ConversionFactor: 10⁻³ C + Symbol: mC + CommonCode: D86 + Description: '' + conversion: + factor: 0.001 + base_units: + - A8 +- Status: '' + LevelAndCategory: 1S + Name: millimole per kilogram + ConversionFactor: 10⁻³ mol/kg + Symbol: mmol/kg + CommonCode: D87 + Description: '' + conversion: + factor: 0.001 + base_units: + - C19 +- Status: '' + LevelAndCategory: 1S + Name: millicoulomb per cubic metre + ConversionFactor: 10⁻³ C/m³ + Symbol: mC/m³ + CommonCode: D88 + Description: '' + conversion: + factor: 0.001 + base_units: + - A29 +- Status: '' + LevelAndCategory: 1S + Name: millicoulomb per square metre + ConversionFactor: 10⁻³ C/m² + Symbol: mC/m² + CommonCode: D89 + Description: '' + conversion: + factor: 0.001 + base_units: + - A34 +- Status: D + LevelAndCategory: '2,3.9' + Name: dyne per square centimetre + ConversionFactor: 10⁻¹ Pa + Symbol: dyn/cm² + CommonCode: D9 + Description: '' + conversion: + factor: 0.1 + base_units: + - C55 + - PAL +- Status: X + LevelAndCategory: '3.1' + Name: cubic metre (net) + ConversionFactor: '' + Symbol: '' + CommonCode: D90 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: rem + ConversionFactor: 10⁻² Sv + Symbol: rem + CommonCode: D91 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: band + ConversionFactor: '' + Symbol: '' + CommonCode: D92 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: second per cubic metre + ConversionFactor: s/m³ + Symbol: s/m³ + CommonCode: D93 + Description: '' + conversion: + factor: 1.0 + base_units: + - D93 +- Status: '' + LevelAndCategory: '1' + Name: second per cubic metre radian + ConversionFactor: s/(rad x m³) + Symbol: s/(rad·m³) + CommonCode: D94 + Description: '' + conversion: + factor: 1.0 + base_units: + - D94 +- Status: '' + LevelAndCategory: 1S + Name: joule per gram + ConversionFactor: J/(10⁻³ x kg) + Symbol: J/g + CommonCode: D95 + Description: '' + conversion: + factor: 1.0 + base_units: + - D95 +- Status: X + LevelAndCategory: '3.1' + Name: pound gross + ConversionFactor: '' + Symbol: '' + CommonCode: D96 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: pallet/unit load + ConversionFactor: '' + Symbol: '' + CommonCode: D97 + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: mass pound + ConversionFactor: '' + Symbol: '' + CommonCode: D98 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: sleeve + ConversionFactor: '' + Symbol: '' + CommonCode: D99 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: decare + ConversionFactor: 10³ m² + Symbol: daa + CommonCode: DAA + Description: '' + conversion: + factor: 1000.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: '3.2' + Name: ten day + ConversionFactor: '' + Symbol: '' + CommonCode: DAD + Description: A unit of time defining the number of days in multiples of 10. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: day + ConversionFactor: 86 400 s + Symbol: d + CommonCode: DAY + Description: '' + conversion: + factor: 86400.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.1' + Name: dry pound + ConversionFactor: '' + Symbol: '' + CommonCode: DB + Description: A unit of mass defining the number of pounds of a product, disregarding + the water content of the product. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: disk (disc) + ConversionFactor: '' + Symbol: '' + CommonCode: DC + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: degree [unit of angle] + ConversionFactor: 1,745 329 x 10⁻² rad + Symbol: "°" + CommonCode: DD + Description: '' + conversion: + factor: 0.01745329 + base_units: + - C81 +- Status: X + LevelAndCategory: '3.9' + Name: deal + ConversionFactor: '' + Symbol: '' + CommonCode: DE + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: decade + ConversionFactor: '' + Symbol: '' + CommonCode: DEC + Description: 'A unit of count defining the number of decades (decade: quantity equal + to 10 or time equal to 10 years).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: decigram + ConversionFactor: 10⁻⁴ kg + Symbol: dg + CommonCode: DG + Description: '' + conversion: + factor: 0.0001 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.3' + Name: dispenser + ConversionFactor: '' + Symbol: '' + CommonCode: DI + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: decagram + ConversionFactor: 10⁻² kg + Symbol: dag + CommonCode: DJ + Description: '' + conversion: + factor: 0.01 + base_units: + - KGM +- Status: '' + LevelAndCategory: 1M + Name: decilitre + ConversionFactor: 10⁻⁴ m³ + Symbol: dl + CommonCode: DLT + Description: '' + conversion: + factor: 0.0001 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: cubic decametre + ConversionFactor: 10³ m³ + Symbol: dam³ + CommonCode: DMA + Description: '' + conversion: + factor: 1000.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: square decimetre + ConversionFactor: 10⁻² m² + Symbol: dm² + CommonCode: DMK + Description: '' + conversion: + factor: 0.01 + base_units: + - MTK +- Status: '' + LevelAndCategory: '3.1' + Name: standard kilolitre + ConversionFactor: '' + Symbol: '' + CommonCode: DMO + Description: A unit of volume defining the number of kilolitres of a product at + a temperature of 15 degrees Celsius, especially in relation to hydrocarbon oils. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre + ConversionFactor: 10⁻³ m³ + Symbol: dm³ + CommonCode: DMQ + Description: '' + conversion: + factor: 0.001 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: decimetre + ConversionFactor: 10⁻¹ m + Symbol: dm + CommonCode: DMT + Description: '' + conversion: + factor: 0.1 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1S + Name: decinewton metre + ConversionFactor: 10⁻¹ N x m + Symbol: dN·m + CommonCode: DN + Description: '' + conversion: + factor: 0.1 + base_units: + - NU +- Status: '' + LevelAndCategory: '3.2' + Name: dozen piece + ConversionFactor: '' + Symbol: '' + CommonCode: DPC + Description: 'A unit of count defining the number of pieces in multiples of 12 (piece: + a single item, article or exemplar).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.2' + Name: dozen pair + ConversionFactor: '' + Symbol: '' + CommonCode: DPR + Description: 'A unit of count defining the number of pairs in multiples of 12 (pair: + item described by two''s).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: displacement tonnage + ConversionFactor: '' + Symbol: '' + CommonCode: DPT + Description: A unit of mass defining the volume of sea water a ship displaces, expressed + as the number of tons. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.6' + Name: data record + ConversionFactor: '' + Symbol: '' + CommonCode: DQ + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: drum + ConversionFactor: '' + Symbol: '' + CommonCode: DR + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: dram (US) + ConversionFactor: 3,887 935 g + Symbol: '' + CommonCode: DRA + Description: 'Synonym: drachm (UK), troy dram' + conversion: + factor: 3.887935 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: dram (UK) + ConversionFactor: 1,771 745 g + Symbol: '' + CommonCode: DRI + Description: 'Synonym: avoirdupois dram' + conversion: + factor: 1.771745 + base_units: [] +- Status: '' + LevelAndCategory: '3.2' + Name: dozen roll + ConversionFactor: '' + Symbol: '' + CommonCode: DRL + Description: A unit of count defining the number of rolls, expressed in twelve roll + units. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: drachm (UK) + ConversionFactor: 3,887 935 g + Symbol: '' + CommonCode: DRM + Description: '' + conversion: + factor: 3.887935 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: display + ConversionFactor: '' + Symbol: '' + CommonCode: DS + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: dry ton + ConversionFactor: '' + Symbol: '' + CommonCode: DT + Description: A unit of mass defining the number of tons of a product, disregarding + the water content of the product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M,3.5 + Name: decitonne + ConversionFactor: 10² kg + Symbol: dt or dtn + CommonCode: DTN + Description: 'Synonym: centner, metric 100 kg; quintal, metric 100 kg' + conversion: + factor: 100.0 + base_units: + - KGM +- Status: D + LevelAndCategory: '2' + Name: dyne + ConversionFactor: 10⁻⁵ N + Symbol: dyn + CommonCode: DU + Description: '' + conversion: + factor: 1.0e-05 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: pennyweight + ConversionFactor: 1,555 174 g + Symbol: '' + CommonCode: DWT + Description: '' + conversion: + factor: 1.555174 + base_units: [] +- Status: D + LevelAndCategory: '2' + Name: dyne per centimetre + ConversionFactor: 10⁻³ N/m + Symbol: dyn/cm + CommonCode: DX + Description: '' + conversion: + factor: 0.001 + base_units: + - 4P +- Status: X + LevelAndCategory: '3.9' + Name: directory book + ConversionFactor: '' + Symbol: '' + CommonCode: DY + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: dozen + ConversionFactor: '12' + Symbol: DOZ + CommonCode: DZN + Description: A unit of count defining the number of units in multiples of 12. + conversion: + factor: 12.0 +- Status: '' + LevelAndCategory: '3.2' + Name: dozen pack + ConversionFactor: '' + Symbol: '' + CommonCode: DZP + Description: 'A unit of count defining the number of packs in multiples of 12 (pack: + standard packaging unit).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: newton per square centimetre + ConversionFactor: 10⁴ Pa + Symbol: N/cm² + CommonCode: E01 + Description: A measure of pressure expressed in newtons per square centimetre. + conversion: + factor: 10000.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.1' + Name: megawatt hour per hour + ConversionFactor: '' + Symbol: MW·h/h + CommonCode: E07 + Description: A unit of accumulated energy of a million watts over a period of one + hour. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: megawatt per hertz + ConversionFactor: '' + Symbol: MW/Hz + CommonCode: E08 + Description: A unit of energy expressed as the load change in million watts that + will cause a frequency shift of one hertz. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: milliampere hour + ConversionFactor: 3,6 C + Symbol: mA·h + CommonCode: E09 + Description: A unit of power load delivered at the rate of one thousandth of an + ampere over a period of one hour. + conversion: + factor: 3.6 + base_units: + - A8 +- Status: '' + LevelAndCategory: '3.5' + Name: degree day + ConversionFactor: '' + Symbol: deg da + CommonCode: E10 + Description: A unit of measure used in meteorology and engineering to measure the + demand for heating or cooling over a given period of days. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.5' + Name: gigacalorie + ConversionFactor: 10⁹ cal + Symbol: '' + CommonCode: E11 + Description: A unit of heat energy equal to one thousand million calories. + conversion: + factor: 1000000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.9' + Name: mille + ConversionFactor: '' + Symbol: '' + CommonCode: E12 + Description: A unit of count defining the number of cigarettes in units of 1000. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (international table) + ConversionFactor: 4,186 8 x 10³ J + Symbol: kcalIT + CommonCode: E14 + Description: A unit of heat energy equal to one thousand calories. + conversion: + factor: 4186.8 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (thermochemical) per hour + ConversionFactor: 1,162 22 W + Symbol: kcalth/h + CommonCode: E15 + Description: A unit of energy equal to one thousand calories per hour. + conversion: + factor: 1.16222 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '3.1' + Name: million Btu(IT) per hour + ConversionFactor: 293 071,1 W + Symbol: BtuIT/h + CommonCode: E16 + Description: A unit of power equal to one million British thermal units per hour. + conversion: + factor: 293071.1 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '3.1' + Name: cubic foot per second + ConversionFactor: 2,831 685 x 10⁻² m³/s + Symbol: ft³/s + CommonCode: E17 + Description: A unit of volume equal to one cubic foot passing a given point in a + period of one second. + conversion: + factor: 0.02831685 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: tonne per hour + ConversionFactor: 2,777 78 x 10⁻¹ kg/s + Symbol: t/h + CommonCode: E18 + Description: A unit of weight or mass equal to one tonne per hour. + conversion: + factor: 0.277778 + base_units: + - KGS +- Status: '' + LevelAndCategory: '3.1' + Name: ping + ConversionFactor: 3,305 m² + Symbol: '' + CommonCode: E19 + Description: A unit of area equal to 3.3 square metres. + conversion: + factor: 3.305 + base_units: + - MTK +- Status: X + LevelAndCategory: '3.9' + Name: belt + ConversionFactor: '' + Symbol: '' + CommonCode: E2 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: megabit per second + ConversionFactor: '' + Symbol: Mbit/s + CommonCode: E20 + Description: A unit of information equal to 10⁶ (1000000) bits (binary digits) per + second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: shares + ConversionFactor: '' + Symbol: '' + CommonCode: E21 + Description: 'A unit of count defining the number of shares (share: a total or portion + of the parts into which a business entity’s capital is divided).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: TEU + ConversionFactor: '' + Symbol: '' + CommonCode: E22 + Description: A unit of count defining the number of twenty-foot equivalent units + (TEUs) as a measure of containerized cargo capacity. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: tyre + ConversionFactor: '' + Symbol: '' + CommonCode: E23 + Description: A unit of count defining the number of tyres (a solid or air-filled + covering placed around a wheel rim to form a soft contact with the road, absorb + shock and provide traction). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: active unit + ConversionFactor: '' + Symbol: '' + CommonCode: E25 + Description: A unit of count defining the number of active units within a substance. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: dose + ConversionFactor: '' + Symbol: '' + CommonCode: E27 + Description: 'A unit of count defining the number of doses (dose: a definite quantity + of a medicine or drug).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: air dry ton + ConversionFactor: '' + Symbol: '' + CommonCode: E28 + Description: A unit of mass defining the number of tons of a product, disregarding + the water content of the product. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: trailer + ConversionFactor: '' + Symbol: '' + CommonCode: E3 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: strand + ConversionFactor: '' + Symbol: '' + CommonCode: E30 + Description: 'A unit of count defining the number of strands (strand: long, thin, + flexible, single thread, strip of fibre, constituent filament or multiples of + the same, twisted together).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: square metre per litre + ConversionFactor: '' + Symbol: m²/l + CommonCode: E31 + Description: A unit of count defining the number of square metres per litre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: litre per hour + ConversionFactor: 2,777 78 x 10⁻⁷ m³/s + Symbol: l/h + CommonCode: E32 + Description: A unit of count defining the number of litres per hour. + conversion: + factor: 2.77778e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '3.1' + Name: foot per thousand + ConversionFactor: 3,048 x 10⁻⁴ m + Symbol: '' + CommonCode: E33 + Description: A unit of count defining the number of feet per thousand units. + conversion: + factor: 0.0003048 + base_units: + - MTR +- Status: '' + LevelAndCategory: '3.6' + Name: gigabyte + ConversionFactor: '' + Symbol: Gbyte + CommonCode: E34 + Description: A unit of information equal to 10⁹ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: terabyte + ConversionFactor: '' + Symbol: Tbyte + CommonCode: E35 + Description: A unit of information equal to 10¹² bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: petabyte + ConversionFactor: '' + Symbol: Pbyte + CommonCode: E36 + Description: A unit of information equal to 10¹⁵ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: pixel + ConversionFactor: '' + Symbol: '' + CommonCode: E37 + Description: 'A unit of count defining the number of pixels (pixel: picture element).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: megapixel + ConversionFactor: '' + Symbol: '' + CommonCode: E38 + Description: A unit of count equal to 10⁶ (1000000) pixels (picture elements). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: dots per inch + ConversionFactor: '' + Symbol: dpi + CommonCode: E39 + Description: A unit of information defining the number of dots per linear inch as + a measure of the resolution or sharpness of a graphic image. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: gross kilogram + ConversionFactor: '' + Symbol: '' + CommonCode: E4 + Description: A unit of mass defining the total number of kilograms before deductions. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: part per hundred thousand + ConversionFactor: 1 x 10⁻⁵ + Symbol: ppht + CommonCode: E40 + Description: A unit of proportion equal to 10⁻⁵. + conversion: + factor: 1.0e-05 +- Status: '' + LevelAndCategory: '2' + Name: kilogram-force per square millimetre + ConversionFactor: 9,806 65 x 10⁶ Pa + Symbol: kgf/mm² + CommonCode: E41 + Description: A unit of pressure defining the number of kilograms force per square + millimetre. + conversion: + factor: 9806650.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: kilogram-force per square centimetre + ConversionFactor: 9,806 65 x 10⁴ Pa + Symbol: kgf/cm² + CommonCode: E42 + Description: A unit of pressure defining the number of kilograms force per square + centimetre. + conversion: + factor: 98066.5 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1M + Name: joule per square centimetre + ConversionFactor: 10⁴ J/m² + Symbol: J/cm² + CommonCode: E43 + Description: A unit of energy defining the number of joules per square centimetre. + conversion: + factor: 10000.0 + base_units: + - B13 +- Status: '' + LevelAndCategory: '3.5' + Name: kilogram-force metre per square centimetre + ConversionFactor: '' + Symbol: kgf·m/cm² + CommonCode: E44 + Description: A unit of torsion defining the torque kilogram-force metre per square + centimetre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: milliohm + ConversionFactor: 10⁻³ Ω + Symbol: mΩ + CommonCode: E45 + Description: '' + conversion: + factor: 0.001 + base_units: + - OHM +- Status: '' + LevelAndCategory: '3.1' + Name: kilowatt hour per cubic metre + ConversionFactor: 3,6 x 10⁶ J/m³ + Symbol: kW·h/m³ + CommonCode: E46 + Description: A unit of energy consumption expressed as kilowatt hour per cubic metre. + conversion: + factor: 3600000.0 + base_units: + - B8 +- Status: '' + LevelAndCategory: '3.1' + Name: kilowatt hour per kelvin + ConversionFactor: 3,6 x 10⁶ J/K + Symbol: kW·h/K + CommonCode: E47 + Description: A unit of energy consumption expressed as kilowatt hour per kelvin. + conversion: + factor: 3600000.0 + base_units: + - JE +- Status: '' + LevelAndCategory: '3.5' + Name: service unit + ConversionFactor: '' + Symbol: '' + CommonCode: E48 + Description: 'A unit of count defining the number of service units (service unit: + defined period / property / facility / utility of supply).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: working day + ConversionFactor: '' + Symbol: '' + CommonCode: E49 + Description: 'A unit of count defining the number of working days (working day: + a day on which work is ordinarily performed).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: metric long ton + ConversionFactor: '' + Symbol: '' + CommonCode: E5 + Description: Use ton (UK) or long ton (US) (common code LTN) + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: accounting unit + ConversionFactor: '' + Symbol: '' + CommonCode: E50 + Description: A unit of count defining the number of accounting units. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: job + ConversionFactor: '' + Symbol: '' + CommonCode: E51 + Description: A unit of count defining the number of jobs. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: run foot + ConversionFactor: '' + Symbol: '' + CommonCode: E52 + Description: A unit of count defining the number feet per run. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: test + ConversionFactor: '' + Symbol: '' + CommonCode: E53 + Description: A unit of count defining the number of tests. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: trip + ConversionFactor: '' + Symbol: '' + CommonCode: E54 + Description: A unit of count defining the number of trips. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: use + ConversionFactor: '' + Symbol: '' + CommonCode: E55 + Description: A unit of count defining the number of times an object is used. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: well + ConversionFactor: '' + Symbol: '' + CommonCode: E56 + Description: A unit of count defining the number of wells. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: zone + ConversionFactor: '' + Symbol: '' + CommonCode: E57 + Description: A unit of count defining the number of zones. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: exabit per second + ConversionFactor: '' + Symbol: Ebit/s + CommonCode: E58 + Description: A unit of information equal to 10¹⁸ bits (binary digits) per second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: exbibyte + ConversionFactor: '' + Symbol: Eibyte + CommonCode: E59 + Description: A unit of information equal to 2⁶⁰ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: pebibyte + ConversionFactor: '' + Symbol: Pibyte + CommonCode: E60 + Description: A unit of information equal to 2⁵⁰ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: tebibyte + ConversionFactor: '' + Symbol: Tibyte + CommonCode: E61 + Description: A unit of information equal to 2⁴⁰ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: gibibyte + ConversionFactor: '' + Symbol: Gibyte + CommonCode: E62 + Description: A unit of information equal to 2³⁰ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: mebibyte + ConversionFactor: '' + Symbol: Mibyte + CommonCode: E63 + Description: A unit of information equal to 2²⁰ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: kibibyte + ConversionFactor: '' + Symbol: Kibyte + CommonCode: E64 + Description: A unit of information equal to 2¹⁰ bytes. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: exbibit per metre + ConversionFactor: '' + Symbol: Eibit/m + CommonCode: E65 + Description: A unit of information equal to 2⁶⁰ bits (binary digits) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: exbibit per square metre + ConversionFactor: '' + Symbol: Eibit/m² + CommonCode: E66 + Description: A unit of information equal to 2⁶⁰ bits (binary digits) per square + metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: exbibit per cubic metre + ConversionFactor: '' + Symbol: Eibit/m³ + CommonCode: E67 + Description: A unit of information equal to 2⁶⁰ bits (binary digits) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: gigabyte per second + ConversionFactor: '' + Symbol: Gbyte/s + CommonCode: E68 + Description: A unit of information equal to 10⁹ bytes per second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: gibibit per metre + ConversionFactor: '' + Symbol: Gibit/m + CommonCode: E69 + Description: A unit of information equal to 2³⁰ bits (binary digits) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: gibibit per square metre + ConversionFactor: '' + Symbol: Gibit/m² + CommonCode: E70 + Description: A unit of information equal to 2³⁰ bits (binary digits) per square + metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: gibibit per cubic metre + ConversionFactor: '' + Symbol: Gibit/m³ + CommonCode: E71 + Description: A unit of information equal to 2³⁰ bits (binary digits) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: kibibit per metre + ConversionFactor: '' + Symbol: Kibit/m + CommonCode: E72 + Description: A unit of information equal to 2¹⁰ bits (binary digits) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: kibibit per square metre + ConversionFactor: '' + Symbol: Kibit/m² + CommonCode: E73 + Description: A unit of information equal to 2¹⁰ bits (binary digits) per square + metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: kibibit per cubic metre + ConversionFactor: '' + Symbol: Kibit/m³ + CommonCode: E74 + Description: A unit of information equal to 2¹⁰ bits (binary digits) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: mebibit per metre + ConversionFactor: '' + Symbol: Mibit/m + CommonCode: E75 + Description: A unit of information equal to 2²⁰ bits (binary digits) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: mebibit per square metre + ConversionFactor: '' + Symbol: Mibit/m² + CommonCode: E76 + Description: A unit of information equal to 2²⁰ bits (binary digits) per square + metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: mebibit per cubic metre + ConversionFactor: '' + Symbol: Mibit/m³ + CommonCode: E77 + Description: A unit of information equal to 2²⁰ bits (binary digits) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: petabit + ConversionFactor: '' + Symbol: Pbit + CommonCode: E78 + Description: A unit of information equal to 10¹⁵ bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: petabit per second + ConversionFactor: '' + Symbol: Pbit/s + CommonCode: E79 + Description: A unit of information equal to 10¹⁵ bits (binary digits) per second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: pebibit per metre + ConversionFactor: '' + Symbol: Pibit/m + CommonCode: E80 + Description: A unit of information equal to 2⁵⁰ bits (binary digits) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: pebibit per square metre + ConversionFactor: '' + Symbol: Pibit/m² + CommonCode: E81 + Description: A unit of information equal to 2⁵⁰ bits (binary digits) per square + metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: pebibit per cubic metre + ConversionFactor: '' + Symbol: Pibit/m³ + CommonCode: E82 + Description: A unit of information equal to 2⁵⁰ bits (binary digits) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: terabit + ConversionFactor: '' + Symbol: Tbit + CommonCode: E83 + Description: A unit of information equal to 10¹² bits (binary digits). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: terabit per second + ConversionFactor: '' + Symbol: Tbit/s + CommonCode: E84 + Description: A unit of information equal to 10¹² bits (binary digits) per second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: tebibit per metre + ConversionFactor: '' + Symbol: Tibit/m + CommonCode: E85 + Description: A unit of information equal to 2⁴⁰ bits (binary digits) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: tebibit per cubic metre + ConversionFactor: '' + Symbol: Tibit/m³ + CommonCode: E86 + Description: A unit of information equal to 2⁴⁰ bits (binary digits) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: tebibit per square metre + ConversionFactor: '' + Symbol: Tibit/m² + CommonCode: E87 + Description: A unit of information equal to 2⁴⁰ bits (binary digits) per square + metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: bit per metre + ConversionFactor: '' + Symbol: bit/m + CommonCode: E88 + Description: A unit of information equal to 1 bit (binary digit) per metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: bit per square metre + ConversionFactor: '' + Symbol: bit/m² + CommonCode: E89 + Description: A unit of information equal to 1 bit (binary digit) per square metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: reciprocal centimetre + ConversionFactor: 10² m⁻¹ + Symbol: cm⁻¹ + CommonCode: E90 + Description: '' + conversion: + factor: 100.0 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '3.1' + Name: reciprocal day + ConversionFactor: 1,157 41 × 10⁻⁵ s⁻¹ + Symbol: d⁻¹ + CommonCode: E91 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre per hour + ConversionFactor: 2,777 78 × 10⁻⁷ m³ x s⁻¹ + Symbol: dm³/h + CommonCode: E92 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: kilogram per hour + ConversionFactor: 2,777 78 × 10⁻⁴ kg x s⁻¹ + Symbol: kg/h + CommonCode: E93 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: kilomole per second + ConversionFactor: 10³ s⁻¹ x mol + Symbol: kmol/s + CommonCode: E94 + Description: '' + conversion: + factor: 1000.0 + base_units: + - KAT +- Status: '' + LevelAndCategory: 1S + Name: mole per second + ConversionFactor: s⁻¹ x mol + Symbol: mol/s + CommonCode: E95 + Description: '' + conversion: + factor: 1.0 + base_units: + - E95 +- Status: '' + LevelAndCategory: 1M + Name: degree per second + ConversionFactor: 1,745 329 x 10⁻² rad x s⁻¹ + Symbol: "°/s" + CommonCode: E96 + Description: '' + conversion: + factor: 0.01745329 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millimetre per degree Celcius metre + ConversionFactor: 10⁻³ K⁻¹ + Symbol: mm/(°C·m) + CommonCode: E97 + Description: '' + conversion: + factor: 0.001 + base_units: + - C91 + - N83 +- Status: '' + LevelAndCategory: 1M + Name: degree Celsius per kelvin + ConversionFactor: '1.0' + Symbol: "°C/K" + CommonCode: E98 + Description: '' + conversion: + factor: 1.0 + base_units: + - E98 + - F02 + - H60 + - L52 + - M91 +- Status: '' + LevelAndCategory: 1M + Name: hectopascal per bar + ConversionFactor: 10⁻³ + Symbol: hPa/bar + CommonCode: E99 + Description: '' + conversion: + factor: 0.001 +- Status: '' + LevelAndCategory: '3.2' + Name: each + ConversionFactor: '' + Symbol: '' + CommonCode: EA + Description: A unit of count defining the number of items regarded as separate units. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: electronic mail box + ConversionFactor: '' + Symbol: '' + CommonCode: EB + Description: A unit of count defining the number of electronic mail boxes. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: each per month + ConversionFactor: '' + Symbol: '' + CommonCode: EC + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.2' + Name: eleven pack + ConversionFactor: '' + Symbol: '' + CommonCode: EP + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: equivalent gallon + ConversionFactor: '' + Symbol: '' + CommonCode: EQ + Description: A unit of volume defining the number of gallons of product produced + from concentrate. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: envelope + ConversionFactor: '' + Symbol: '' + CommonCode: EV + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: bit per cubic metre + ConversionFactor: '' + Symbol: bit/m³ + CommonCode: F01 + Description: A unit of information equal to 1 bit (binary digit) per cubic metre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: kelvin per kelvin + ConversionFactor: '1.0' + Symbol: K/K + CommonCode: F02 + Description: '' + conversion: + factor: 1.0 + base_units: + - E98 + - F02 + - H60 + - L52 + - M91 +- Status: '' + LevelAndCategory: 1M + Name: kilopascal per bar + ConversionFactor: 10⁻² + Symbol: kPa/bar + CommonCode: F03 + Description: '' + conversion: + factor: 0.01 +- Status: '' + LevelAndCategory: 1M + Name: millibar per bar + ConversionFactor: 10⁻³ + Symbol: mbar/bar + CommonCode: F04 + Description: '' + conversion: + factor: 0.001 +- Status: '' + LevelAndCategory: 1M + Name: megapascal per bar + ConversionFactor: 10¹ + Symbol: MPa/bar + CommonCode: F05 + Description: '' + conversion: + factor: 10.0 +- Status: '' + LevelAndCategory: '2' + Name: poise per bar + ConversionFactor: 10⁻⁶ s + Symbol: P/bar + CommonCode: F06 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: pascal per bar + ConversionFactor: 10⁻⁵ + Symbol: Pa/bar + CommonCode: F07 + Description: '' + conversion: + factor: 1.0e-05 +- Status: '' + LevelAndCategory: '2' + Name: milliampere per inch + ConversionFactor: 3,937 007 874 015 75 x 10⁻² A x m⁻¹ + Symbol: mA/in + CommonCode: F08 + Description: '' + conversion: + factor: 0.0393700787401575 + base_units: [] +- Status: X + LevelAndCategory: '3.8' + Name: thousand cubic foot per day + ConversionFactor: '' + Symbol: '' + CommonCode: F1 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: kelvin per hour + ConversionFactor: 2,777 78 × 10⁻⁴ s⁻¹ x K + Symbol: K/h + CommonCode: F10 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kelvin per minute + ConversionFactor: 1,666 67 × 10⁻² s⁻¹ x K + Symbol: K/min + CommonCode: F11 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kelvin per second + ConversionFactor: s⁻¹ x K + Symbol: K/s + CommonCode: F12 + Description: '' + conversion: + factor: 1.0 + base_units: + - F12 +- Status: '' + LevelAndCategory: '2' + Name: slug + ConversionFactor: 1,459 390 x 10¹ kg + Symbol: slug + CommonCode: F13 + Description: A unit of mass. One slug is the mass accelerated at 1 foot per second + per second by a force of 1 pound. + conversion: + factor: 14.5939 + base_units: + - KGM +- Status: '' + LevelAndCategory: 1M + Name: gram per kelvin + ConversionFactor: 10⁻³ kg x K⁻¹ + Symbol: g/K + CommonCode: F14 + Description: '' + conversion: + factor: 0.001 + base_units: + - F15 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per kelvin + ConversionFactor: kg x K⁻¹ + Symbol: kg/K + CommonCode: F15 + Description: '' + conversion: + factor: 1.0 + base_units: + - F15 +- Status: '' + LevelAndCategory: 1M + Name: milligram per kelvin + ConversionFactor: 10⁻⁶ kg x K⁻¹ + Symbol: mg/K + CommonCode: F16 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - F15 +- Status: '' + LevelAndCategory: '2' + Name: pound-force per foot + ConversionFactor: 1,459 39 × 10¹ kg x s⁻² + Symbol: lbf/ft + CommonCode: F17 + Description: '' + conversion: + factor: 1.45939 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram square centimetre + ConversionFactor: 10⁻⁴ kg m² + Symbol: kg·cm² + CommonCode: F18 + Description: '' + conversion: + factor: 0.0001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram square millimetre + ConversionFactor: 10⁻⁶ kg m² + Symbol: kg·mm² + CommonCode: F19 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound inch squared + ConversionFactor: 2,926 397 x 10⁻⁴ kg x m² + Symbol: lb·in² + CommonCode: F20 + Description: '' + conversion: + factor: 0.0002926397 + base_units: + - B32 +- Status: '' + LevelAndCategory: '2' + Name: pound-force inch + ConversionFactor: 1,129 85 × 10⁻¹ kg x m² x s⁻² + Symbol: lbf·in + CommonCode: F21 + Description: '' + conversion: + factor: 1.12985 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound-force foot per ampere + ConversionFactor: 1,355 82 kg x m² x s⁻² x A⁻¹ + Symbol: lbf·ft/A + CommonCode: F22 + Description: '' + conversion: + factor: 1.35582 + base_units: + - F90 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic decimetre + ConversionFactor: kg x m⁻³ + Symbol: g/dm³ + CommonCode: F23 + Description: '' + conversion: + factor: 1.0 + base_units: + - F23 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per kilomol + ConversionFactor: 10⁻³ kg x mol⁻¹ + Symbol: kg/kmol + CommonCode: F24 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per hertz + ConversionFactor: 10⁻³ kg x s + Symbol: g/Hz + CommonCode: F25 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per day + ConversionFactor: 1,157 41 × 10⁻⁸ kg x s⁻¹ + Symbol: g/d + CommonCode: F26 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per hour + ConversionFactor: 2,777 78 × 10⁻⁷ kg x s⁻¹ + Symbol: g/h + CommonCode: F27 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per minute + ConversionFactor: 1,666 67 × 10⁻⁵ kg x s⁻¹ + Symbol: g/min + CommonCode: F28 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per second + ConversionFactor: 10⁻³ kg x s⁻¹ + Symbol: g/s + CommonCode: F29 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per day + ConversionFactor: 1,157 41 × 10⁻⁵ kg x s⁻¹ + Symbol: kg/d + CommonCode: F30 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per minute + ConversionFactor: 1,666 67 × 10⁻² kg x s⁻¹ + Symbol: kg/min + CommonCode: F31 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per day + ConversionFactor: 1,157 41 × 10⁻¹¹ kg x s⁻¹ + Symbol: mg/d + CommonCode: F32 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per minute + ConversionFactor: 1,666 67 × 10⁻⁸ kg x s⁻¹ + Symbol: mg/min + CommonCode: F33 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per second + ConversionFactor: 10⁻⁶ kg x s⁻¹ + Symbol: mg/s + CommonCode: F34 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per day kelvin + ConversionFactor: 1,157 41 × 10⁻⁸ kg x s⁻¹ x K⁻¹ + Symbol: g/(d·K) + CommonCode: F35 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per hour kelvin + ConversionFactor: 2,777 78 × 10⁻⁷ kg x s⁻¹ x K⁻¹ + Symbol: g/(h·K) + CommonCode: F36 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per minute kelvin + ConversionFactor: 1,666 67 × 10⁻⁵ kg x s⁻¹ x K⁻¹ + Symbol: g/(min·K) + CommonCode: F37 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per second kelvin + ConversionFactor: 10⁻³ kg x s⁻¹ x K⁻¹ + Symbol: g/(s·K) + CommonCode: F38 + Description: '' + conversion: + factor: 0.001 + base_units: + - F42 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per day kelvin + ConversionFactor: 1,157 41 × 10⁻⁵ kg x s⁻¹ x K⁻¹ + Symbol: kg/(d·K) + CommonCode: F39 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per hour kelvin + ConversionFactor: 2,777 78 × 10⁻⁴ kg x s⁻¹ x K⁻¹ + Symbol: kg/(h·K) + CommonCode: F40 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per minute kelvin + ConversionFactor: 1,666 67 × 10⁻²kg x s⁻¹ x K⁻¹ + Symbol: kg/(min·K) + CommonCode: F41 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per second kelvin + ConversionFactor: kg x s⁻¹ x K⁻¹ + Symbol: kg/(s·K) + CommonCode: F42 + Description: '' + conversion: + factor: 1.0 + base_units: + - F42 +- Status: '' + LevelAndCategory: 1M + Name: milligram per day kelvin + ConversionFactor: 1,157 41 × 10⁻¹¹ kg x s⁻¹ x K⁻¹ + Symbol: mg/(d·K) + CommonCode: F43 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per hour kelvin + ConversionFactor: 2,777 78 × 10⁻¹⁰ kg x s⁻¹ x K⁻¹ + Symbol: mg/(h·K) + CommonCode: F44 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per minute kelvin + ConversionFactor: 1,666 67 × 10⁻⁸ kg x s⁻¹ x K⁻¹ + Symbol: mg/(min·K) + CommonCode: F45 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per second kelvin + ConversionFactor: 10⁻⁶ kg x s⁻¹ x K⁻¹ + Symbol: mg/(s·K) + CommonCode: F46 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - F42 +- Status: '' + LevelAndCategory: 1M + Name: newton per millimetre + ConversionFactor: 10³ kg x s⁻² + Symbol: N/mm + CommonCode: F47 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound-force per inch + ConversionFactor: 1,751 27 × 10² kg x s⁻² + Symbol: lbf/in + CommonCode: F48 + Description: '' + conversion: + factor: 1.75127 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: rod [unit of distance] + ConversionFactor: 5,029 210 m + Symbol: rd (US) + CommonCode: F49 + Description: A unit of distance equal to 5.5 yards (16 feet 6 inches). + conversion: + factor: 5.02921 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1M + Name: micrometre per kelvin + ConversionFactor: 10⁻⁶ m x K⁻¹ + Symbol: µm/K + CommonCode: F50 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - F52 +- Status: '' + LevelAndCategory: 1M + Name: centimetre per kelvin + ConversionFactor: 10⁻² m x K⁻¹ + Symbol: cm/K + CommonCode: F51 + Description: '' + conversion: + factor: 0.01 + base_units: + - F52 +- Status: '' + LevelAndCategory: 1M + Name: metre per kelvin + ConversionFactor: m x K⁻¹ + Symbol: m/K + CommonCode: F52 + Description: '' + conversion: + factor: 1.0 + base_units: + - F52 +- Status: '' + LevelAndCategory: 1M + Name: millimetre per kelvin + ConversionFactor: 10⁻³ m x K⁻¹ + Symbol: mm/K + CommonCode: F53 + Description: '' + conversion: + factor: 0.001 + base_units: + - F52 +- Status: '' + LevelAndCategory: 1M + Name: milliohm per metre + ConversionFactor: 10⁻³ Ω/m + Symbol: mΩ/m + CommonCode: F54 + Description: '' + conversion: + factor: 0.001 + base_units: + - H26 +- Status: '' + LevelAndCategory: '2' + Name: ohm per mile (statute mile) + ConversionFactor: 6,213 71 × 10⁻⁴  Ω/m + Symbol: Ω/mi + CommonCode: F55 + Description: '' + conversion: + factor: 6.21371 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: ohm per kilometre + ConversionFactor: 10⁻³ Ω/m + Symbol: Ω/km + CommonCode: F56 + Description: '' + conversion: + factor: 0.001 + base_units: + - H26 +- Status: '' + LevelAndCategory: '2' + Name: milliampere per pound-force per square inch + ConversionFactor: 1,450 38 × 10⁻⁷ kg⁻¹ x m x s² x A + Symbol: mA/(lbf/in²) + CommonCode: F57 + Description: '' + conversion: + factor: 1.45038 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: reciprocal bar + ConversionFactor: bar⁻¹ + Symbol: 1/bar + CommonCode: F58 + Description: '' + conversion: + factor: 1.0 + base_units: + - F58 +- Status: '' + LevelAndCategory: 1M + Name: milliampere per bar + ConversionFactor: 10⁻⁸ kg⁻¹ x m x s² x A + Symbol: mA/bar + CommonCode: F59 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - N93 +- Status: '' + LevelAndCategory: 1M + Name: degree Celsius per bar + ConversionFactor: 10⁻⁵ kg⁻¹ x m x s² x K + Symbol: "°C/bar" + CommonCode: F60 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - N79 +- Status: '' + LevelAndCategory: 1M + Name: kelvin per bar + ConversionFactor: 10⁻⁵ kg⁻¹ x m x s² x K + Symbol: K/bar + CommonCode: F61 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - N79 +- Status: '' + LevelAndCategory: 1M + Name: gram per day bar + ConversionFactor: 1,157 41 × 10⁻¹³ m x s + Symbol: g/(d·bar) + CommonCode: F62 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per hour bar + ConversionFactor: 2,777 78 × 10⁻¹² m x s + Symbol: g/(h·bar) + CommonCode: F63 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per minute bar + ConversionFactor: 1,666 67 × 10⁻¹⁰ m x s + Symbol: g/(min·bar) + CommonCode: F64 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per second bar + ConversionFactor: 10⁻⁸ m x s + Symbol: g/(s·bar) + CommonCode: F65 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - M87 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per day bar + ConversionFactor: 1,157 41 × 10⁻¹⁰ m x s + Symbol: kg/(d·bar) + CommonCode: F66 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per hour bar + ConversionFactor: 2,777 78 × 10⁻⁹ m x s + Symbol: kg/(h·bar) + CommonCode: F67 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per minute bar + ConversionFactor: 1,666 67 × 10⁻⁷ m x s + Symbol: kg/(min·bar) + CommonCode: F68 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per second bar + ConversionFactor: 10⁻⁵ m x s + Symbol: kg/(s·bar) + CommonCode: F69 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M87 +- Status: '' + LevelAndCategory: 1M + Name: milligram per day bar + ConversionFactor: 1,157 41 × 10⁻¹⁶ m x s + Symbol: mg/(d·bar) + CommonCode: F70 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per hour bar + ConversionFactor: 2,777 78 × 10⁻¹⁵ m x s + Symbol: mg/(h·bar) + CommonCode: F71 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per minute bar + ConversionFactor: 1,666 67 × 10⁻¹³ m x s + Symbol: mg/(min·bar) + CommonCode: F72 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligram per second bar + ConversionFactor: 10⁻¹¹ m x s + Symbol: mg/(s·bar) + CommonCode: F73 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - M87 +- Status: '' + LevelAndCategory: 1M + Name: gram per bar + ConversionFactor: 10⁻⁸ m x s² + Symbol: g/bar + CommonCode: F74 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - M74 +- Status: '' + LevelAndCategory: 1M + Name: milligram per bar + ConversionFactor: 10⁻¹¹ m x s² + Symbol: mg/bar + CommonCode: F75 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - M74 +- Status: '' + LevelAndCategory: 1M + Name: milliampere per millimetre + ConversionFactor: m⁻¹ x A + Symbol: mA/mm + CommonCode: F76 + Description: '' + conversion: + factor: 1.0 + base_units: + - F76 +- Status: '' + LevelAndCategory: 1M + Name: pascal second per kelvin + ConversionFactor: kg x m⁻¹ x s⁻¹ x K⁻¹ + Symbol: Pa.s/K + CommonCode: F77 + Description: '' + conversion: + factor: 1.0 + base_units: + - F77 +- Status: '' + LevelAndCategory: '2' + Name: inch of water + ConversionFactor: 2,490 89 × 10² kg x m⁻¹ x s⁻² + Symbol: inH₂O + CommonCode: F78 + Description: '' + conversion: + factor: 2.49089 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: inch of mercury + ConversionFactor: 3,386 39 × 10³ kg x m⁻¹ x s⁻² + Symbol: inHg + CommonCode: F79 + Description: '' + conversion: + factor: 3.38639 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: water horse power + ConversionFactor: 7,460 43 x 10² W + Symbol: '' + CommonCode: F80 + Description: A unit of power defining the amount of power required to move a given + volume of water against acceleration of gravity to a specified elevation (pressure + head). + conversion: + factor: 746.043 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1M + Name: bar per kelvin + ConversionFactor: 10⁵ kg x m⁻¹ x s⁻² x K⁻¹ + Symbol: bar/K + CommonCode: F81 + Description: '' + conversion: + factor: 100000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: hectopascal per kelvin + ConversionFactor: 10² kg x m⁻¹ x s⁻² x K⁻¹ + Symbol: hPa/K + CommonCode: F82 + Description: '' + conversion: + factor: 100.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilopascal per kelvin + ConversionFactor: 10³ kg x m⁻¹ x s⁻² x K⁻¹ + Symbol: kPa/K + CommonCode: F83 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millibar per kelvin + ConversionFactor: 10² kg x m⁻¹ x s⁻² x K⁻¹ + Symbol: mbar/K + CommonCode: F84 + Description: '' + conversion: + factor: 100.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: megapascal per kelvin + ConversionFactor: 10⁶ kg x m⁻¹ x s⁻² x K⁻¹ + Symbol: MPa/K + CommonCode: F85 + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: poise per kelvin + ConversionFactor: 10⁻¹ kg x m⁻¹ x s⁻¹ x K⁻¹ + Symbol: P/K + CommonCode: F86 + Description: '' + conversion: + factor: 0.1 + base_units: + - F77 +- Status: '' + LevelAndCategory: 1M + Name: volt per litre minute + ConversionFactor: 1,666 67 × 10¹ kg x m⁻¹ x s⁻⁴ x A⁻¹ + Symbol: V/(l·min) + CommonCode: F87 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: newton centimetre + ConversionFactor: 10⁻² kg x m² x s⁻² + Symbol: N·cm + CommonCode: F88 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: newton metre per degree + ConversionFactor: 57,295 788 kg x m² x s⁻² x rad⁻¹ + Symbol: Nm/° + CommonCode: F89 + Description: '' + conversion: + factor: 57.295788 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: fibre per cubic centimetre of air + ConversionFactor: '' + Symbol: '' + CommonCode: F9 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: newton metre per ampere + ConversionFactor: kg x m² x s⁻² x A⁻¹ + Symbol: N·m/A + CommonCode: F90 + Description: '' + conversion: + factor: 1.0 + base_units: + - F90 +- Status: '' + LevelAndCategory: 1M + Name: bar litre per second + ConversionFactor: 10² kg x m² x s⁻³ + Symbol: bar·l/s + CommonCode: F91 + Description: '' + conversion: + factor: 100.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: bar cubic metre per second + ConversionFactor: 10⁵ kg x m² x s⁻³ + Symbol: bar·m³/s + CommonCode: F92 + Description: '' + conversion: + factor: 100000.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: hectopascal litre per second + ConversionFactor: 10⁻¹ kg x m² x s⁻³ + Symbol: hPa·l/s + CommonCode: F93 + Description: '' + conversion: + factor: 0.1 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: hectopascal cubic metre per second + ConversionFactor: 10² kg x m² x s⁻³ + Symbol: hPa·m³/s + CommonCode: F94 + Description: '' + conversion: + factor: 100.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: millibar litre per second + ConversionFactor: 10⁻¹ kg x m² x s⁻³ + Symbol: mbar·l/s + CommonCode: F95 + Description: '' + conversion: + factor: 0.1 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: millibar cubic metre per second + ConversionFactor: 10² kg x m² x s⁻³ + Symbol: mbar·m³/s + CommonCode: F96 + Description: '' + conversion: + factor: 100.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: megapascal litre per second + ConversionFactor: 10³ kg x m² x s⁻³ + Symbol: MPa·l/s + CommonCode: F97 + Description: '' + conversion: + factor: 1000.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: megapascal cubic metre per second + ConversionFactor: 10⁶ kg x m² x s⁻³ + Symbol: MPa·m³/s + CommonCode: F98 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: pascal litre per second + ConversionFactor: 10⁻³ kg x m² x s⁻³ + Symbol: Pa·l/s + CommonCode: F99 + Description: '' + conversion: + factor: 0.001 + base_units: + - G01 +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit + ConversionFactor: 5/9 x K + Symbol: "°F" + CommonCode: FAH + Description: 'Refer ISO 80000-5 (Quantities and units — Part 5: Thermodynamics)' + conversion: + factor: 5.0 + base_units: [] +- Status: '' + LevelAndCategory: '1' + Name: farad + ConversionFactor: F + Symbol: F + CommonCode: FAR + Description: '' + conversion: + factor: 1.0 + base_units: + - FAR +- Status: X + LevelAndCategory: '3.9' + Name: field + ConversionFactor: '' + Symbol: '' + CommonCode: FB + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: fibre metre + ConversionFactor: '' + Symbol: '' + CommonCode: FBM + Description: A unit of length defining the number of metres of individual fibre. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: thousand cubic foot + ConversionFactor: '' + Symbol: kft³ + CommonCode: FC + Description: A unit of volume equal to one thousand cubic foot. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: million particle per cubic foot + ConversionFactor: '' + Symbol: '' + CommonCode: FD + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: track foot + ConversionFactor: '' + Symbol: '' + CommonCode: FE + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: hundred cubic metre + ConversionFactor: '' + Symbol: '' + CommonCode: FF + Description: A unit of volume equal to one hundred cubic metres. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: transdermal patch + ConversionFactor: '' + Symbol: '' + CommonCode: FG + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: micromole + ConversionFactor: 10⁻⁶ mol + Symbol: µmol + CommonCode: FH + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - C34 +- Status: '' + LevelAndCategory: '3.8' + Name: failures in time + ConversionFactor: 2,777 78 × 10⁻¹³ s⁻¹ + Symbol: '' + CommonCode: FIT + Description: A unit of count defining the number of failures that can be expected + over a specified time interval. Failure rates of semiconductor components are + often specified as FIT (failures in time unit) where 1 FIT = 10⁻⁹ /h. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: flake ton + ConversionFactor: '' + Symbol: '' + CommonCode: FL + Description: 'A unit of mass defining the number of tons of a flaked substance (flake: + a small flattish fragment).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: million cubic foot + ConversionFactor: '' + Symbol: Mft³ + CommonCode: FM + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: foot + ConversionFactor: 0,304 8 m + Symbol: ft + CommonCode: FOT + Description: '' + conversion: + factor: 0.3048 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: pound per square foot + ConversionFactor: 4,882 428 kg/m² + Symbol: lb/ft² + CommonCode: FP + Description: '' + conversion: + factor: 4.882428 + base_units: + - '28' +- Status: '' + LevelAndCategory: '2' + Name: foot per minute + ConversionFactor: 5,08 x 10⁻³ m/s + Symbol: ft/min + CommonCode: FR + Description: '' + conversion: + factor: 0.00508 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: foot per second + ConversionFactor: 0,304 8 m/s + Symbol: ft/s + CommonCode: FS + Description: '' + conversion: + factor: 0.3048 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: square foot + ConversionFactor: 9,290 304 x 10⁻² m² + Symbol: ft² + CommonCode: FTK + Description: '' + conversion: + factor: 0.09290304 + base_units: + - MTK +- Status: '' + LevelAndCategory: '2' + Name: cubic foot + ConversionFactor: 2,831 685 x 10⁻² m³ + Symbol: ft³ + CommonCode: FTQ + Description: '' + conversion: + factor: 0.02831685 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: pascal cubic metre per second + ConversionFactor: kg x m² x s⁻³ + Symbol: Pa·m³/s + CommonCode: G01 + Description: '' + conversion: + factor: 1.0 + base_units: + - G01 +- Status: '' + LevelAndCategory: 1M + Name: centimetre per bar + ConversionFactor: 10⁻⁷ kg⁻¹ x m² x s² + Symbol: cm/bar + CommonCode: G04 + Description: '' + conversion: + factor: 1.0e-07 + base_units: + - M53 +- Status: '' + LevelAndCategory: 1M + Name: metre per bar + ConversionFactor: 10⁻⁵ kg⁻¹ x m² x s² + Symbol: m/bar + CommonCode: G05 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M53 +- Status: '' + LevelAndCategory: 1M + Name: millimetre per bar + ConversionFactor: 10⁻⁸ kg⁻¹ x m² x s² + Symbol: mm/bar + CommonCode: G06 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - M53 +- Status: '' + LevelAndCategory: '2' + Name: square inch per second + ConversionFactor: 6,451 6 × 10⁻⁴ m² x s⁻¹ + Symbol: in²/s + CommonCode: G08 + Description: '' + conversion: + factor: 6.4516 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: square metre per second kelvin + ConversionFactor: m² x s⁻¹ x K⁻¹ + Symbol: m²/(s·K) + CommonCode: G09 + Description: '' + conversion: + factor: 1.0 + base_units: + - G09 +- Status: '' + LevelAndCategory: '2' + Name: stokes per kelvin + ConversionFactor: 10⁻⁴ m² x s⁻¹ x K⁻¹ + Symbol: St/K + CommonCode: G10 + Description: '' + conversion: + factor: 0.0001 + base_units: + - G09 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic centimetre bar + ConversionFactor: 10⁻² m⁻² x s² + Symbol: g/(cm³·bar) + CommonCode: G11 + Description: '' + conversion: + factor: 0.01 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic decimetre bar + ConversionFactor: 10⁻⁵ m⁻² x s² + Symbol: g/(dm³·bar) + CommonCode: G12 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: gram per litre bar + ConversionFactor: 10⁻⁵ m⁻² x s² + Symbol: g/(l·bar) + CommonCode: G13 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic metre bar + ConversionFactor: 10⁻⁸ m⁻² x s² + Symbol: g/(m³·bar) + CommonCode: G14 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: gram per millilitre bar + ConversionFactor: 10⁻² m⁻² x s² + Symbol: g/(ml·bar) + CommonCode: G15 + Description: '' + conversion: + factor: 0.01 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic centimetre bar + ConversionFactor: 10¹ m⁻² x s² + Symbol: kg/(cm³·bar) + CommonCode: G16 + Description: '' + conversion: + factor: 10.0 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per litre bar + ConversionFactor: 10⁻² m⁻² x s² + Symbol: kg/(l·bar) + CommonCode: G17 + Description: '' + conversion: + factor: 0.01 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic metre bar + ConversionFactor: 10⁻⁵ m⁻² x s² + Symbol: kg/(m³·bar) + CommonCode: G18 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M73 +- Status: '' + LevelAndCategory: 1M + Name: newton metre per kilogram + ConversionFactor: m² x s⁻² + Symbol: N·m/kg + CommonCode: G19 + Description: '' + conversion: + factor: 1.0 + base_units: + - G19 +- Status: '' + LevelAndCategory: '2' + Name: US gallon per minute + ConversionFactor: 6,309 020 x 10⁻⁵ m³/s + Symbol: gal (US) /min + CommonCode: G2 + Description: '' + conversion: + factor: 6.30902e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pound-force foot per pound + ConversionFactor: 2,989 07 m² x s⁻² + Symbol: lbf·ft/lb + CommonCode: G20 + Description: '' + conversion: + factor: 2.98907 + base_units: + - G19 +- Status: '' + LevelAndCategory: '2' + Name: cup [unit of volume] + ConversionFactor: 2,365 882 x 10⁻⁴ m³ + Symbol: cup (US) + CommonCode: G21 + Description: '' + conversion: + factor: 0.0002365882 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: peck + ConversionFactor: 8,809 768 x 10⁻³ m³ + Symbol: pk (US) + CommonCode: G23 + Description: '' + conversion: + factor: 0.008809768 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: tablespoon (US) + ConversionFactor: 1,478 676 x 10⁻⁵ m³ + Symbol: tablespoon (US) + CommonCode: G24 + Description: '' + conversion: + factor: 1.478676e-05 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: teaspoon (US) + ConversionFactor: 4,928 922 x 10⁻⁶ m³ + Symbol: teaspoon (US) + CommonCode: G25 + Description: '' + conversion: + factor: 4.928922e-06 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: stere + ConversionFactor: m³ + Symbol: st + CommonCode: G26 + Description: '' + conversion: + factor: 1.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per kelvin + ConversionFactor: 10⁻⁶ m³ x K⁻¹ + Symbol: cm³/K + CommonCode: G27 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - G29 +- Status: '' + LevelAndCategory: 1M + Name: litre per kelvin + ConversionFactor: 10⁻³ m³ x K⁻¹ + Symbol: l/K + CommonCode: G28 + Description: '' + conversion: + factor: 0.001 + base_units: + - G29 +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per kelvin + ConversionFactor: m³ x K⁻¹ + Symbol: m³/K + CommonCode: G29 + Description: '' + conversion: + factor: 1.0 + base_units: + - G29 +- Status: '' + LevelAndCategory: '2' + Name: Imperial gallon per minute + ConversionFactor: 7,576 82 x 10⁻⁵ m³/s + Symbol: gal (UK) /min + CommonCode: G3 + Description: '' + conversion: + factor: 7.57682e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1M + Name: millilitre per kelvin + ConversionFactor: 10⁻⁶ m³ x K⁻¹ + Symbol: ml/K + CommonCode: G30 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - G29 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic centimetre + ConversionFactor: 10⁶ kg x m⁻³ + Symbol: kg/cm³ + CommonCode: G31 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - F23 +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per cubic yard + ConversionFactor: 3,707 98 × 10⁻² kg x m⁻³ + Symbol: oz/yd³ + CommonCode: G32 + Description: '' + conversion: + factor: 3.70798 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic centimetre kelvin + ConversionFactor: 10³ kg x m⁻³ x K⁻¹ + Symbol: g/(cm³·K) + CommonCode: G33 + Description: '' + conversion: + factor: 1000.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic decimetre kelvin + ConversionFactor: kg x m⁻³ x K⁻¹ + Symbol: g/(dm³·K) + CommonCode: G34 + Description: '' + conversion: + factor: 1.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: gram per litre kelvin + ConversionFactor: kg x m⁻³ x K⁻¹ + Symbol: g/(l·K) + CommonCode: G35 + Description: '' + conversion: + factor: 1.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: gram per cubic metre kelvin + ConversionFactor: 10⁻³ kg x m⁻³ x K⁻¹ + Symbol: g/(m³·K) + CommonCode: G36 + Description: '' + conversion: + factor: 0.001 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: gram per millilitre kelvin + ConversionFactor: 10³ kg x m⁻³ x K⁻¹ + Symbol: g/(ml·K) + CommonCode: G37 + Description: '' + conversion: + factor: 1000.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic centimetre kelvin + ConversionFactor: 10⁶ kg x m⁻³ x K⁻¹ + Symbol: kg/(cm³·K) + CommonCode: G38 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per litre kelvin + ConversionFactor: 10³ kg x m⁻³ x K⁻¹ + Symbol: kg/(l·K) + CommonCode: G39 + Description: '' + conversion: + factor: 1000.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic metre kelvin + ConversionFactor: kg x m⁻³ x K⁻¹ + Symbol: kg/(m³·K) + CommonCode: G40 + Description: '' + conversion: + factor: 1.0 + base_units: + - G34 + - G35 + - G40 +- Status: '' + LevelAndCategory: 1M + Name: square metre per second bar + ConversionFactor: 10⁻⁵ kg⁻¹ x m³ x s + Symbol: m²/(s·bar) + CommonCode: G41 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M82 +- Status: '' + LevelAndCategory: 1M + Name: microsiemens per centimetre + ConversionFactor: 10⁻⁴ S/m + Symbol: µS/cm + CommonCode: G42 + Description: '' + conversion: + factor: 0.0001 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1M + Name: microsiemens per metre + ConversionFactor: 10⁻⁶ S/m + Symbol: µS/m + CommonCode: G43 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1M + Name: nanosiemens per centimetre + ConversionFactor: 10⁻⁷ S/m + Symbol: nS/cm + CommonCode: G44 + Description: '' + conversion: + factor: 1.0e-07 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1M + Name: nanosiemens per metre + ConversionFactor: 10⁻⁹ S/m + Symbol: nS/m + CommonCode: G45 + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - D10 +- Status: '' + LevelAndCategory: '2' + Name: stokes per bar + ConversionFactor: 10⁻⁹ kg⁻¹ x m³ x s + Symbol: St/bar + CommonCode: G46 + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - M82 +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per day + ConversionFactor: 1,157 41 × 10⁻¹¹ m³ x s⁻¹ + Symbol: cm³/d + CommonCode: G47 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per hour + ConversionFactor: 2,777 78 × 10⁻¹⁰ m³ x s⁻¹ + Symbol: cm³/h + CommonCode: G48 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per minute + ConversionFactor: 1,666 67 × 10⁻⁸ m³ x s⁻¹ + Symbol: cm³/min + CommonCode: G49 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: gallon (US) per hour + ConversionFactor: 1,051 5 × 10⁻⁶ m³ x s⁻¹ + Symbol: gal/h + CommonCode: G50 + Description: '' + conversion: + factor: 1.0515 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per second + ConversionFactor: 10⁻³ m³ x s⁻¹ + Symbol: l/s + CommonCode: G51 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per day + ConversionFactor: 1,157 41 × 10⁻⁵ m³ x s⁻¹ + Symbol: m³/d + CommonCode: G52 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per minute + ConversionFactor: 1,666 67 × 10⁻² m³ x s⁻¹ + Symbol: m³/min + CommonCode: G53 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per day + ConversionFactor: 1,157 41 × 10⁻¹¹ m³ x s⁻¹ + Symbol: ml/d + CommonCode: G54 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per hour + ConversionFactor: 2,777 78 × 10⁻¹⁰ m³ x s⁻¹ + Symbol: ml/h + CommonCode: G55 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: cubic inch per hour + ConversionFactor: 4,551 96 × 10⁻⁹ m³ x s⁻¹ + Symbol: in³/h + CommonCode: G56 + Description: '' + conversion: + factor: 4.55196 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: cubic inch per minute + ConversionFactor: 2,731 18 × 10⁻⁷ m³ x s⁻¹ + Symbol: in³/min + CommonCode: G57 + Description: '' + conversion: + factor: 2.73118 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: cubic inch per second + ConversionFactor: 1,638 71 × 10⁻⁵ m³ x s⁻¹ + Symbol: in³/s + CommonCode: G58 + Description: '' + conversion: + factor: 1.63871 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milliampere per litre minute + ConversionFactor: 1,666 67 × 10⁻² m⁻³ x s⁻¹ x A + Symbol: mA/(l·min) + CommonCode: G59 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: volt per bar + ConversionFactor: 10⁻⁵ m³ x s⁻¹ x A⁻¹ + Symbol: V/bar + CommonCode: G60 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - N98 +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per day kelvin + ConversionFactor: 1,157 41 × 10⁻¹¹ m³ x s⁻¹ x K⁻¹ + Symbol: cm³/(d·K) + CommonCode: G61 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per hour kelvin + ConversionFactor: 2,777 78 × 10⁻¹⁰ m³ x s⁻¹ x K⁻¹ + Symbol: cm³/(h·K) + CommonCode: G62 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per minute kelvin + ConversionFactor: 1,666 67 × 10⁻⁸ m³ x s⁻¹ x K⁻¹ + Symbol: cm³/(min·K) + CommonCode: G63 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per second kelvin + ConversionFactor: 10⁻⁶ m³ x s⁻¹ x K⁻¹ + Symbol: cm³/(s·K) + CommonCode: G64 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - G72 +- Status: '' + LevelAndCategory: 1M + Name: litre per day kelvin + ConversionFactor: 1,157 41 × 10⁻⁸ m³ x s⁻¹ x K⁻¹ + Symbol: l/(d·K) + CommonCode: G65 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per hour kelvin + ConversionFactor: 2,777 78 × 10⁻⁷ m³ x s⁻¹ x K⁻¹ + Symbol: l/(h·K) + CommonCode: G66 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per minute kelvin + ConversionFactor: 1,666 67 × 10⁻⁵ m³ x s⁻¹ x K⁻¹ + Symbol: l/(min·K) + CommonCode: G67 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per second kelvin + ConversionFactor: 10⁻³ m³ x s⁻¹ x K⁻¹ + Symbol: l/(s·K) + CommonCode: G68 + Description: '' + conversion: + factor: 0.001 + base_units: + - G72 +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per day kelvin + ConversionFactor: 1,157 41 × 10⁻⁵ m³ x s⁻¹ x K⁻¹ + Symbol: m³/(d·K) + CommonCode: G69 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: microfiche sheet + ConversionFactor: '' + Symbol: '' + CommonCode: G7 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per hour kelvin + ConversionFactor: 2,777 78 × 10⁻⁴ m³ x s⁻¹ x K⁻¹ + Symbol: m³/(h·K) + CommonCode: G70 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per minute kelvin + ConversionFactor: 1,666 67 × 10⁻² m³ x s⁻¹ x K⁻¹ + Symbol: m³/(min·K) + CommonCode: G71 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per second kelvin + ConversionFactor: m³ x s⁻¹ x K⁻¹ + Symbol: m³/(s·K) + CommonCode: G72 + Description: '' + conversion: + factor: 1.0 + base_units: + - G72 +- Status: '' + LevelAndCategory: 1M + Name: millilitre per day kelvin + ConversionFactor: 1,157 41 × 10⁻¹¹ m³ x s⁻¹ x K⁻¹ + Symbol: ml/(d·K) + CommonCode: G73 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per hour kelvin + ConversionFactor: 2,777 78 × 10⁻¹⁰ m³ x s⁻¹ x K⁻¹ + Symbol: ml/(h·K) + CommonCode: G74 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per minute kelvin + ConversionFactor: 1,666 67 × 10⁻⁸ m³ x s⁻¹ x K⁻¹ + Symbol: ml/(min·K) + CommonCode: G75 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per second kelvin + ConversionFactor: 10⁻⁶ m³ x s⁻¹ x K⁻¹ + Symbol: ml/(s·K) + CommonCode: G76 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - G72 +- Status: '' + LevelAndCategory: 1M + Name: millimetre to the fourth power + ConversionFactor: 10⁻¹² m⁴ + Symbol: mm⁴ + CommonCode: G77 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - B83 +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per day bar + ConversionFactor: 1,157 41 × 10⁻¹⁶ kg⁻¹ x m⁴ x s + Symbol: cm³/(d·bar) + CommonCode: G78 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per hour bar + ConversionFactor: 2,777 78 × 10⁻¹⁵ kg⁻¹ x m⁴ x s + Symbol: cm³/(h·bar) + CommonCode: G79 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per minute bar + ConversionFactor: 1,666 67 × 10⁻¹³ kg⁻¹ x m⁴ x s + Symbol: cm³/(min·bar) + CommonCode: G80 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per second bar + ConversionFactor: 10⁻¹¹ kg⁻¹ x m⁴ x s + Symbol: cm³/(s·bar) + CommonCode: G81 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - N45 +- Status: '' + LevelAndCategory: 1M + Name: litre per day bar + ConversionFactor: 1,157 41 × 10⁻¹³ kg⁻¹ x m⁴ x s + Symbol: l/(d·bar) + CommonCode: G82 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per hour bar + ConversionFactor: 2,777 78 × 10⁻¹² kg⁻¹ x m⁴ x s + Symbol: l/(h·bar) + CommonCode: G83 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per minute bar + ConversionFactor: 1,666 67 × 10⁻¹⁰ kg⁻¹ x m⁴ x s + Symbol: l/(min·bar) + CommonCode: G84 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per second bar + ConversionFactor: 10⁻⁸ kg⁻¹ x m⁴ x s + Symbol: l/(s·bar) + CommonCode: G85 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - N45 +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per day bar + ConversionFactor: 1,157 41 × 10⁻¹⁰ kg⁻¹ x m⁴ x s + Symbol: m³/(d·bar) + CommonCode: G86 + Description: '' + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per hour bar + ConversionFactor: 2,777 78 × 10⁻⁹ kg⁻¹ x m⁴ x s + Symbol: m³/(h·bar) + CommonCode: G87 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per minute bar + ConversionFactor: 1,666 67 × 10⁻⁷ kg⁻¹ x m⁴ x s + Symbol: m³/(min·bar) + CommonCode: G88 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per second bar + ConversionFactor: 10⁻⁵ kg⁻¹ x m⁴ x s + Symbol: m³/(s·bar) + CommonCode: G89 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - N45 +- Status: '' + LevelAndCategory: 1M + Name: millilitre per day bar + ConversionFactor: 1,157 41 x 10⁻¹⁶ x kg⁻¹ x m⁴ x s + Symbol: ml/(d·bar) + CommonCode: G90 + Description: '' + conversion: + factor: 1.15741e-16 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per hour bar + ConversionFactor: 2,777 78 x 10⁻¹⁵ x kg⁻¹ x m⁴ x s + Symbol: ml/(h·bar) + CommonCode: G91 + Description: '' + conversion: + factor: 2.77778e-15 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per minute bar + ConversionFactor: 1,666 67 × 10⁻¹³ x kg⁻¹ x m⁴ x s + Symbol: ml/(min·bar) + CommonCode: G92 + Description: '' + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millilitre per second bar + ConversionFactor: 10⁻¹¹ kg⁻¹ x m⁴ x s + Symbol: ml/(s·bar) + CommonCode: G93 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - N45 +- Status: '' + LevelAndCategory: 1M + Name: cubic centimetre per bar + ConversionFactor: 10⁻¹¹ kg⁻¹ x m⁴ x s² + Symbol: cm³/bar + CommonCode: G94 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - M71 +- Status: '' + LevelAndCategory: 1M + Name: litre per bar + ConversionFactor: 10⁻⁸ kg⁻¹ x m⁴ x s² + Symbol: l/bar + CommonCode: G95 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - M71 +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per bar + ConversionFactor: 10⁻⁵ kg⁻¹ x m⁴ x s² + Symbol: m³/bar + CommonCode: G96 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M71 +- Status: '' + LevelAndCategory: 1M + Name: millilitre per bar + ConversionFactor: 10⁻¹¹ kg⁻¹ x m⁴ x s² + Symbol: ml/bar + CommonCode: G97 + Description: '' + conversion: + factor: 1.0e-11 + base_units: + - M71 +- Status: '' + LevelAndCategory: 1M + Name: microhenry per kiloohm + ConversionFactor: 10⁻⁹ s + Symbol: µH/kΩ + CommonCode: G98 + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: microhenry per ohm + ConversionFactor: 10⁻⁶ s + Symbol: µH/Ω + CommonCode: G99 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.5' + Name: gallon (US) per day + ConversionFactor: 4,381 264 x 10⁻⁸ m³/s + Symbol: gal (US)/d + CommonCode: GB + Description: '' + conversion: + factor: 4.381264e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1M + Name: gigabecquerel + ConversionFactor: 10⁹ Bq + Symbol: GBq + CommonCode: GBQ + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: X + LevelAndCategory: '3.7' + Name: gram per 100 gram + ConversionFactor: '' + Symbol: '' + CommonCode: GC + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: gross barrel + ConversionFactor: '' + Symbol: '' + CommonCode: GD + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: gram, dry weight + ConversionFactor: '' + Symbol: '' + CommonCode: GDW + Description: A unit of mass defining the number of grams of a product, disregarding + the water content of the product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: pound per gallon (US) + ConversionFactor: 1,198 264 x 10² kg/m³ + Symbol: lb/gal (US) + CommonCode: GE + Description: '' + conversion: + factor: 119.8264 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1M + Name: gram per metre (gram per 100 centimetres) + ConversionFactor: 10⁻³ kg/m + Symbol: g/m + CommonCode: GF + Description: '' + conversion: + factor: 0.001 + base_units: + - KL +- Status: '' + LevelAndCategory: '3.1' + Name: gram of fissile isotope + ConversionFactor: '' + Symbol: gi F/S + CommonCode: GFI + Description: 'A unit of mass defining the number of grams of a fissile isotope (fissile + isotope: an isotope whose nucleus is able to be split when irradiated with low + energy neutrons).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: great gross + ConversionFactor: '1728' + Symbol: '' + CommonCode: GGR + Description: A unit of count defining the number of units in multiples of 1728 (12 + x 12 x 12). + conversion: + factor: 1728.0 +- Status: X + LevelAndCategory: '3.8' + Name: half gallon (US) + ConversionFactor: '' + Symbol: '' + CommonCode: GH + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: gill (US) + ConversionFactor: 1,182 941 x 10⁻⁴ m³ + Symbol: gi (US) + CommonCode: GIA + Description: '' + conversion: + factor: 0.0001182941 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.1' + Name: gram, including container + ConversionFactor: '' + Symbol: '' + CommonCode: GIC + Description: A unit of mass defining the number of grams of a product, including + its container. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: gill (UK) + ConversionFactor: 1,420 653 x 10⁻⁴ m³ + Symbol: gi (UK) + CommonCode: GII + Description: '' + conversion: + factor: 0.0001420653 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.1' + Name: gram, including inner packaging + ConversionFactor: '' + Symbol: '' + CommonCode: GIP + Description: A unit of mass defining the number of grams of a product, including + its inner packaging materials. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gram per millilitre + ConversionFactor: 10³ kg/m³ + Symbol: g/ml + CommonCode: GJ + Description: '' + conversion: + factor: 1000.0 + base_units: + - GL + - KMQ +- Status: X + LevelAndCategory: '3.7' + Name: gram per kilogram + ConversionFactor: '' + Symbol: '' + CommonCode: GK + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gram per litre + ConversionFactor: kg/m³ + Symbol: g/l + CommonCode: GL + Description: '' + conversion: + factor: 1.0 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: dry gallon (US) + ConversionFactor: 4,404 884 x 10⁻³ m³ + Symbol: dry gal (US) + CommonCode: GLD + Description: '' + conversion: + factor: 0.004404884 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: gallon (UK) + ConversionFactor: 4,546 092 x 10⁻³ m³ + Symbol: gal (UK) + CommonCode: GLI + Description: '' + conversion: + factor: 0.004546092 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: gallon (US) + ConversionFactor: 3,785 412 x 10⁻³ m³ + Symbol: gal (US) + CommonCode: GLL + Description: '' + conversion: + factor: 0.003785412 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: gram per square metre + ConversionFactor: 10⁻³ kg/m² + Symbol: g/m² + CommonCode: GM + Description: '' + conversion: + factor: 0.001 + base_units: + - '28' +- Status: X + LevelAndCategory: '3.1' + Name: gross gallon + ConversionFactor: '' + Symbol: '' + CommonCode: GN + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: milligram per square metre + ConversionFactor: 10⁻⁶ kg/m² + Symbol: mg/m² + CommonCode: GO + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - '28' +- Status: '' + LevelAndCategory: 1M + Name: milligram per cubic metre + ConversionFactor: 10⁻⁶ kg/m³ + Symbol: mg/m³ + CommonCode: GP + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1M + Name: microgram per cubic metre + ConversionFactor: 10⁻⁹ kg/m³ + Symbol: µg/m³ + CommonCode: GQ + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1S + Name: gram + ConversionFactor: 10⁻³ kg + Symbol: g + CommonCode: GRM + Description: '' + conversion: + factor: 0.001 + base_units: + - KGM +- Status: '' + LevelAndCategory: '2' + Name: grain + ConversionFactor: 64,798 91 x 10⁻⁶ kg + Symbol: gr + CommonCode: GRN + Description: '' + conversion: + factor: 6.479891e-05 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.7' + Name: gross + ConversionFactor: '144' + Symbol: gr + CommonCode: GRO + Description: A unit of count defining the number of units in multiples of 144 (12 + x 12). + conversion: + factor: 144.0 +- Status: D + LevelAndCategory: '3.4' + Name: gross register ton + ConversionFactor: '' + Symbol: '' + CommonCode: GRT + Description: A unit of mass equal to the total cubic footage before deductions, + where 1 register ton is equal to 100 cubic feet. Refer International Convention + on tonnage measurement of ships. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: 3.1,3.4 + Name: gross ton + ConversionFactor: '' + Symbol: '' + CommonCode: GT + Description: 'A unit of mass equal to 2240 pounds. Refer International Convention + on Tonnage measurement of Ships.,Synonym: ton (UK) or long ton (US) (common code + LTN)' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gigajoule + ConversionFactor: 10⁹ J + Symbol: GJ + CommonCode: GV + Description: '' + conversion: + factor: 1000000000.0 + base_units: [] +- Status: X + LevelAndCategory: '3.5' + Name: gallon per thousand cubic foot + ConversionFactor: '' + Symbol: '' + CommonCode: GW + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: gigawatt hour + ConversionFactor: 3,6 x 10¹² J + Symbol: GW·h + CommonCode: GWH + Description: '' + conversion: + factor: 3600000000000.0 + base_units: + - JOU +- Status: X + LevelAndCategory: '3.1' + Name: gross yard + ConversionFactor: '' + Symbol: '' + CommonCode: GY + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: gage system + ConversionFactor: '' + Symbol: '' + CommonCode: GZ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: henry per kiloohm + ConversionFactor: 10⁻³ s + Symbol: H/kΩ + CommonCode: H03 + Description: '' + conversion: + factor: 0.001 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: henry per ohm + ConversionFactor: s + Symbol: H/Ω + CommonCode: H04 + Description: '' + conversion: + factor: 1.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: millihenry per kiloohm + ConversionFactor: 10⁻⁶ s + Symbol: mH/kΩ + CommonCode: H05 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: millihenry per ohm + ConversionFactor: 10⁻³ s + Symbol: mH/Ω + CommonCode: H06 + Description: '' + conversion: + factor: 0.001 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: pascal second per bar + ConversionFactor: 10⁻⁵ s + Symbol: Pa·s/bar + CommonCode: H07 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: microbecquerel + ConversionFactor: 10⁻⁶ Bq + Symbol: µBq + CommonCode: H08 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: reciprocal year + ConversionFactor: 3,168 81 x 10⁻⁸ s⁻¹ + Symbol: 1/y + CommonCode: H09 + Description: '' + conversion: + factor: 3.16881e-08 + base_units: + - C97 +- Status: X + LevelAndCategory: '3.9' + Name: half page – electronic + ConversionFactor: '' + Symbol: '' + CommonCode: H1 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: reciprocal hour + ConversionFactor: 2,777 78 × 10⁻⁴ s⁻¹ + Symbol: 1/h + CommonCode: H10 + Description: '' + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: reciprocal month + ConversionFactor: 3,802 57 × 10⁻⁷ s⁻¹ + Symbol: 1/mo + CommonCode: H11 + Description: '' + conversion: + factor: 3.80257 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: degree Celsius per hour + ConversionFactor: 2,777 78 x 10⁻⁴ s⁻¹ K + Symbol: "°C/h" + CommonCode: H12 + Description: '' + conversion: + factor: 0.000277778 + base_units: + - H14 +- Status: '' + LevelAndCategory: 1M + Name: degree Celsius per minute + ConversionFactor: 1,666 67 x 10⁻² s⁻¹ K + Symbol: "°C/min" + CommonCode: H13 + Description: '' + conversion: + factor: 0.0166667 + base_units: + - H14 +- Status: '' + LevelAndCategory: 1M + Name: degree Celsius per second + ConversionFactor: s⁻¹ K + Symbol: "°C/s" + CommonCode: H14 + Description: '' + conversion: + factor: 1.0 + base_units: + - H14 +- Status: '' + LevelAndCategory: 1M + Name: square centimetre per gram + ConversionFactor: 10⁻¹ kg⁻¹ x m² + Symbol: cm²/g + CommonCode: H15 + Description: '' + conversion: + factor: 0.1 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: square decametre + ConversionFactor: 10² m² + Symbol: dam² + CommonCode: H16 + Description: 'Synonym: are' + conversion: + factor: 100.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: 1S + Name: square hectometre + ConversionFactor: 10⁴ m² + Symbol: hm² + CommonCode: H18 + Description: Synonym: hectare + conversion: + factor: 10000.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: 1S + Name: cubic hectometre + ConversionFactor: 10⁶ m³ + Symbol: hm³ + CommonCode: H19 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.8' + Name: half litre + ConversionFactor: '' + Symbol: '' + CommonCode: H2 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: cubic kilometre + ConversionFactor: 10⁹ m³ + Symbol: km³ + CommonCode: H20 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.2' + Name: blank + ConversionFactor: '' + Symbol: '' + CommonCode: H21 + Description: A unit of count defining the number of blanks. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2.0' + Name: volt square inch per pound-force + ConversionFactor: 1,450 377 439 8 × 10⁻⁴ m³ x s⁻¹ x A⁻¹ + Symbol: V/(lbf/in²) + CommonCode: H22 + Description: '' + conversion: + factor: 1.4503774398 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: volt per inch + ConversionFactor: 3,937 007 874 × 10¹ m x kg x s⁻³ x A⁻¹ + Symbol: V/in + CommonCode: H23 + Description: '' + conversion: + factor: 3.937007874 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: volt per microsecond + ConversionFactor: 10⁶ V/s + Symbol: V/µs + CommonCode: H24 + Description: '' + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.7' + Name: percent per kelvin + ConversionFactor: 10⁻² K⁻¹ + Symbol: "%/K" + CommonCode: H25 + Description: A unit of proportion, equal to 0.01, in relation to the SI base unit + Kelvin. + conversion: + factor: 0.01 + base_units: + - C91 + - N83 +- Status: '' + LevelAndCategory: 1M + Name: ohm per metre + ConversionFactor: Ω/m + Symbol: Ω/m + CommonCode: H26 + Description: '' + conversion: + factor: 1.0 + base_units: + - H26 +- Status: '' + LevelAndCategory: '2' + Name: degree per metre + ConversionFactor: 1,745 329 x 10⁻² rad/m + Symbol: "°/m" + CommonCode: H27 + Description: '' + conversion: + factor: 0.01745329 + base_units: + - C84 +- Status: '' + LevelAndCategory: 1S + Name: microfarad per kilometre + ConversionFactor: 10⁻⁹ F/m + Symbol: µF/km + CommonCode: H28 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: microgram per litre + ConversionFactor: 10⁻⁶ m⁻³ x kg + Symbol: µg/l + CommonCode: H29 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: square micrometre (square micron) + ConversionFactor: 10⁻¹² m² + Symbol: µm² + CommonCode: H30 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - MTK +- Status: '' + LevelAndCategory: '1.0' + Name: ampere per kilogram + ConversionFactor: A x kg⁻¹ + Symbol: A/kg + CommonCode: H31 + Description: '' + conversion: + factor: 1.0 + base_units: + - H31 +- Status: '' + LevelAndCategory: '1.0' + Name: ampere squared second + ConversionFactor: A² x s + Symbol: A²·s + CommonCode: H32 + Description: '' + conversion: + factor: 1.0 + base_units: + - H32 +- Status: '' + LevelAndCategory: 1S + Name: farad per kilometre + ConversionFactor: 10⁻³ F/m + Symbol: F/km + CommonCode: H33 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: hertz metre + ConversionFactor: Hz x m + Symbol: Hz·m + CommonCode: H34 + Description: '' + conversion: + factor: 1.0 + base_units: + - H34 +- Status: '' + LevelAndCategory: '1.0' + Name: kelvin metre per watt + ConversionFactor: K x m⁻¹ x kg⁻¹ x s³ + Symbol: K·m/W + CommonCode: H35 + Description: '' + conversion: + factor: 1.0 + base_units: + - H35 +- Status: '' + LevelAndCategory: 1M + Name: megaohm per kilometre + ConversionFactor: 10³ Ω/m + Symbol: MΩ/km + CommonCode: H36 + Description: '' + conversion: + factor: 1000.0 + base_units: + - H26 +- Status: '' + LevelAndCategory: 1M + Name: megaohm per metre + ConversionFactor: 10⁶ Ω/m + Symbol: MΩ/m + CommonCode: H37 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - H26 +- Status: '' + LevelAndCategory: 1S + Name: megaampere + ConversionFactor: 10⁶ A + Symbol: MA + CommonCode: H38 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - AMP +- Status: '' + LevelAndCategory: '2' + Name: megahertz kilometre + ConversionFactor: 10⁹ Hz x m + Symbol: MHz·km + CommonCode: H39 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - H34 +- Status: '' + LevelAndCategory: '1.0' + Name: newton per ampere + ConversionFactor: kg x m x s⁻² x A⁻¹ + Symbol: N/A + CommonCode: H40 + Description: '' + conversion: + factor: 1.0 + base_units: + - H40 +- Status: '' + LevelAndCategory: '2.0' + Name: newton metre watt to the power minus 0,5 + ConversionFactor: kg x m² x s⁻² x W⁻⁰‧⁵ + Symbol: N·m·W⁻⁰‧⁵ + CommonCode: H41 + Description: '' + conversion: + factor: 1.0 + base_units: + - H41 +- Status: '' + LevelAndCategory: 1M + Name: pascal per metre + ConversionFactor: m⁻² kg x s⁻² + Symbol: Pa/m + CommonCode: H42 + Description: '' + conversion: + factor: 1.0 + base_units: + - H42 +- Status: '' + LevelAndCategory: 1S + Name: siemens per centimetre + ConversionFactor: 10² S/m + Symbol: S/cm + CommonCode: H43 + Description: '' + conversion: + factor: 100.0 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1S + Name: teraohm + ConversionFactor: 10¹² Ω + Symbol: TΩ + CommonCode: H44 + Description: '' + conversion: + factor: 1000000000000.0 + base_units: + - OHM +- Status: '' + LevelAndCategory: '1.0' + Name: volt second per metre + ConversionFactor: m x kg x s⁻² x A⁻¹ + Symbol: V·s/m + CommonCode: H45 + Description: '' + conversion: + factor: 1.0 + base_units: + - H45 +- Status: '' + LevelAndCategory: 1S + Name: volt per second + ConversionFactor: m² x kg x s⁻⁴ x A⁻¹ + Symbol: V/s + CommonCode: H46 + Description: '' + conversion: + factor: 1.0 + base_units: + - H46 +- Status: '' + LevelAndCategory: '1.0' + Name: watt per cubic metre + ConversionFactor: m⁻¹ x kg x s⁻³ + Symbol: W/m³ + CommonCode: H47 + Description: '' + conversion: + factor: 1.0 + base_units: + - H47 +- Status: '' + LevelAndCategory: 1S + Name: attofarad + ConversionFactor: 10⁻¹⁸ m⁻² x kg⁻¹ x s⁴ x A² + Symbol: aF + CommonCode: H48 + Description: '' + conversion: + factor: 1.0e-18 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: centimetre per hour + ConversionFactor: 0,277 777 778 × 10⁻⁶ m x s⁻¹ + Symbol: cm/h + CommonCode: H49 + Description: '' + conversion: + factor: 0.277777778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: reciprocal cubic centimetre + ConversionFactor: 10⁶ m⁻³ + Symbol: cm⁻³ + CommonCode: H50 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - C86 +- Status: '' + LevelAndCategory: '1.0' + Name: decibel per kilometre + ConversionFactor: 10⁻⁴ B/m + Symbol: dB/km + CommonCode: H51 + Description: '' + conversion: + factor: 0.0001 + base_units: + - P43 +- Status: '' + LevelAndCategory: '1.0' + Name: decibel per metre + ConversionFactor: 10⁻¹ B/m + Symbol: dB/m + CommonCode: H52 + Description: '' + conversion: + factor: 0.1 + base_units: + - P43 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per bar + ConversionFactor: 10⁻⁵ m x s² + Symbol: kg/bar + CommonCode: H53 + Description: '' + conversion: + factor: 1.0e-05 + base_units: + - M74 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic decimetre kelvin + ConversionFactor: 10³ m⁻³ x kg x K⁻¹ + Symbol: "(kg/dm³)/K" + CommonCode: H54 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic decimetre bar + ConversionFactor: 10⁻² m⁻² x s² + Symbol: "(kg/dm³)/bar" + CommonCode: H55 + Description: '' + conversion: + factor: 0.01 + base_units: + - M73 +- Status: '' + LevelAndCategory: '1.0' + Name: kilogram per square metre second + ConversionFactor: kg m⁻² x s⁻¹ + Symbol: kg/(m²·s) + CommonCode: H56 + Description: '' + conversion: + factor: 1.0 + base_units: + - H56 +- Status: '' + LevelAndCategory: '2.0' + Name: inch per two pi radiant + ConversionFactor: 2,54 x 10⁻² m/(2 x π x rad) + Symbol: in/revolution + CommonCode: H57 + Description: '' + conversion: + factor: 0.0254 + base_units: [] +- Status: '' + LevelAndCategory: '1.0' + Name: metre per volt second + ConversionFactor: m⁻¹ x kg⁻¹ x s² x A + Symbol: m/(V·s) + CommonCode: H58 + Description: '' + conversion: + factor: 1.0 + base_units: + - H58 +- Status: '' + LevelAndCategory: '1.0' + Name: square metre per newton + ConversionFactor: m x kg⁻¹ x s² + Symbol: m²/N + CommonCode: H59 + Description: '' + conversion: + factor: 1.0 + base_units: + - H59 +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per cubic metre + ConversionFactor: '1.0' + Symbol: m³/m³ + CommonCode: H60 + Description: '' + conversion: + factor: 1.0 + base_units: + - E98 + - F02 + - H60 + - L52 + - M91 +- Status: '' + LevelAndCategory: 1S + Name: millisiemens per centimetre + ConversionFactor: 10⁻¹ S/m + Symbol: mS/cm + CommonCode: H61 + Description: '' + conversion: + factor: 0.1 + base_units: + - D10 +- Status: '' + LevelAndCategory: 1M + Name: millivolt per minute + ConversionFactor: 1,666 666 667 × 10⁻⁵ m² x kg x s⁻⁴ x A⁻¹ + Symbol: mV/min + CommonCode: H62 + Description: '' + conversion: + factor: 1.666666667 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: milligram per square centimetre + ConversionFactor: 10⁻² m⁻² x kg + Symbol: mg/cm² + CommonCode: H63 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: milligram per gram + ConversionFactor: 10⁻³ 1 + Symbol: mg/g + CommonCode: H64 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: millilitre per cubic metre + ConversionFactor: 10⁻⁶ 1 + Symbol: ml/m³ + CommonCode: H65 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2.0' + Name: millimetre per year + ConversionFactor: 3,15576 × 10⁴ m x s⁻¹ + Symbol: mm/y + CommonCode: H66 + Description: '' + conversion: + factor: 3.15576 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: millimetre per hour + ConversionFactor: 0,277 777 778 × 10⁻⁷ m x s⁻¹ + Symbol: mm/h + CommonCode: H67 + Description: '' + conversion: + factor: 0.277777778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millimole per gram + ConversionFactor: mol x kg⁻¹ + Symbol: mmol/g + CommonCode: H68 + Description: '' + conversion: + factor: 1.0 + base_units: + - H68 +- Status: '' + LevelAndCategory: 1M + Name: picopascal per kilometre + ConversionFactor: 10⁻¹⁵ m⁻² x kg x s⁻² + Symbol: pPa/km + CommonCode: H69 + Description: '' + conversion: + factor: 1.0e-15 + base_units: [] +- Status: '' + LevelAndCategory: '1.0' + Name: picosecond + ConversionFactor: 10⁻¹² s + Symbol: ps + CommonCode: H70 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.7' + Name: percent per month + ConversionFactor: '' + Symbol: "%/mo" + CommonCode: H71 + Description: A unit of proportion, equal to 0.01, in relation to a month. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per hectobar + ConversionFactor: '' + Symbol: "%/hbar" + CommonCode: H72 + Description: A unit of proportion, equal to 0.01, in relation to 100-fold of the + unit bar. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per decakelvin + ConversionFactor: 10⁻³ K⁻¹ + Symbol: "%/daK" + CommonCode: H73 + Description: A unit of proportion, equal to 0.01, in relation to 10-fold of the + SI base unit Kelvin. + conversion: + factor: 0.001 + base_units: + - C91 + - N83 +- Status: '' + LevelAndCategory: 1M + Name: watt per metre + ConversionFactor: W m⁻¹ + Symbol: W/m + CommonCode: H74 + Description: '' + conversion: + factor: 1.0 + base_units: + - H74 +- Status: '' + LevelAndCategory: 1M + Name: decapascal + ConversionFactor: 10¹ Pa + Symbol: daPa + CommonCode: H75 + Description: '' + conversion: + factor: 10.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1M + Name: gram per millimetre + ConversionFactor: 10¹ kg x m⁻¹ + Symbol: g/mm + CommonCode: H76 + Description: '' + conversion: + factor: 10.0 + base_units: [] +- Status: '' + LevelAndCategory: '3' + Name: module width + ConversionFactor: '' + Symbol: MW + CommonCode: H77 + Description: A unit of measure used to describe the breadth of electronic assemblies + as an installation standard or mounting dimension. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: conventional centimetre of water + ConversionFactor: 9,806 65 × 10¹ Pa + Symbol: cm H₂O + CommonCode: H78 + Description: '' + conversion: + factor: 9.80665 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: French gauge + ConversionFactor: 0,333 333 333 × 10⁻³ m + Symbol: Fg + CommonCode: H79 + Description: 'A unit of distance used for measuring the diameter of small tubes + such as urological instruments and catheters.,Synonym: French, Charrière, Charrière + gauge' + conversion: + factor: 0.333333333 + base_units: [] +- Status: '' + LevelAndCategory: '3' + Name: rack unit + ConversionFactor: 4,445 × 10⁻² m + Symbol: U or RU + CommonCode: H80 + Description: A unit of measure used to describe the height in rack units of equipment + intended for mounting in a 19-inch rack or a 23-inch rack. One rack unit is 1.75 + inches (44.45 mm) high. + conversion: + factor: 4.445 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: millimetre per minute + ConversionFactor: 1,666 666 667 × 10⁻⁵ m x s⁻¹ + Symbol: mm/min + CommonCode: H81 + Description: '' + conversion: + factor: 1.666666667 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: big point + ConversionFactor: 0,352 777 8 × 10⁻³ m + Symbol: bp + CommonCode: H82 + Description: 'A unit of length defining the number of big points (big point: Adobe + software(US) defines the big point to be exactly 1/72 inch (0.013 888 9 inch or + 0.352 777 8 millimeters))' + conversion: + factor: 0.3527778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: litre per kilogram + ConversionFactor: 10⁻³ m³ x kg⁻¹ + Symbol: l/kg + CommonCode: H83 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram millimetre + ConversionFactor: 10⁻⁶ kg x m + Symbol: g·mm + CommonCode: H84 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - M94 +- Status: '' + LevelAndCategory: 1M + Name: reciprocal week + ConversionFactor: 1,647 989 452 868 × 10⁻⁶ s⁻¹ + Symbol: 1/wk + CommonCode: H85 + Description: '' + conversion: + factor: 1.647989452868 + base_units: [] +- Status: '' + LevelAndCategory: '3.8' + Name: piece + ConversionFactor: '' + Symbol: '' + CommonCode: H87 + Description: 'A unit of count defining the number of pieces (piece: a single item, + article or exemplar).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: megaohm kilometre + ConversionFactor: 10⁹ Ω x m + Symbol: MΩ·km + CommonCode: H88 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - C61 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per ohm + ConversionFactor: 10⁻² Ω⁻¹ + Symbol: "%/Ω" + CommonCode: H89 + Description: A unit of proportion, equal to 0.01, in relation to the SI derived + unit ohm. + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '3.7' + Name: percent per degree + ConversionFactor: 0,572 957 8 rad⁻¹ + Symbol: "%/°" + CommonCode: H90 + Description: A unit of proportion, equal to 0.01, in relation to an angle of one + degree. + conversion: + factor: 0.5729578 + base_units: [] +- Status: '' + LevelAndCategory: '3.7' + Name: percent per ten thousand + ConversionFactor: 10⁻⁶ + Symbol: "%/10000" + CommonCode: H91 + Description: A unit of proportion, equal to 0.01, in relation to multiples of ten + thousand. + conversion: + factor: 1.0e-06 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per one hundred thousand + ConversionFactor: 10⁻⁷ + Symbol: "%/100000" + CommonCode: H92 + Description: A unit of proportion, equal to 0.01, in relation to multiples of one + hundred thousand. + conversion: + factor: 1.0e-07 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per hundred + ConversionFactor: 10⁻⁴ + Symbol: "%/100" + CommonCode: H93 + Description: A unit of proportion, equal to 0.01, in relation to multiples of one + hundred. + conversion: + factor: 0.0001 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per thousand + ConversionFactor: 10⁻⁵ + Symbol: "%/1000" + CommonCode: H94 + Description: A unit of proportion, equal to 0.01, in relation to multiples of one + thousand. + conversion: + factor: 1.0e-05 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per volt + ConversionFactor: 10⁻² V⁻¹ + Symbol: "%/V" + CommonCode: H95 + Description: A unit of proportion, equal to 0.01, in relation to the SI derived + unit volt. + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '3.7' + Name: percent per bar + ConversionFactor: 10⁻⁷ Pa⁻¹ + Symbol: "%/bar" + CommonCode: H96 + Description: A unit of proportion, equal to 0.01, in relation to an atmospheric + pressure of one bar. + conversion: + factor: 1.0e-07 + base_units: + - C96 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per inch + ConversionFactor: 0,393 700 8 m⁻¹ + Symbol: "%/in" + CommonCode: H98 + Description: A unit of proportion, equal to 0.01, in relation to an inch. + conversion: + factor: 0.3937008 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per metre + ConversionFactor: 10⁻² m⁻¹ + Symbol: "%/m" + CommonCode: H99 + Description: A unit of proportion, equal to 0.01, in relation to a metre. + conversion: + factor: 0.01 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '3.9' + Name: hank + ConversionFactor: '' + Symbol: '' + CommonCode: HA + Description: A unit of length, typically for yarn. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2.0' + Name: hectare + ConversionFactor: 10⁴ m² + Symbol: ha + CommonCode: HAR + Description: 'Synonym: square hectometre' + conversion: + factor: 10000.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: 1M + Name: hectobar + ConversionFactor: 10⁷ Pa + Symbol: hbar + CommonCode: HBA + Description: '' + conversion: + factor: 10000000.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.2' + Name: hundred boxes + ConversionFactor: '' + Symbol: '' + CommonCode: HBX + Description: A unit of count defining the number of boxes in multiples of one hundred + box units. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: hundred count + ConversionFactor: '' + Symbol: '' + CommonCode: HC + Description: A unit of count defining the number of units counted in multiples of + 100. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.7' + Name: half dozen + ConversionFactor: '6' + Symbol: '' + CommonCode: HD + Description: '' + conversion: + factor: 6.0 +- Status: '' + LevelAndCategory: '3.1' + Name: hundred kilogram, dry weight + ConversionFactor: '' + Symbol: '' + CommonCode: HDW + Description: A unit of mass defining the number of hundred kilograms of a product, + disregarding the water content of the product. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: hundredth of a carat + ConversionFactor: '' + Symbol: '' + CommonCode: HE + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: head + ConversionFactor: '' + Symbol: '' + CommonCode: HEA + Description: 'A unit of count defining the number of heads (head: a person or animal + considered as one of a number).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: hundred foot + ConversionFactor: '' + Symbol: '' + CommonCode: HF + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: hectogram + ConversionFactor: 10⁻¹ kg + Symbol: hg + CommonCode: HGM + Description: '' + conversion: + factor: 0.1 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.8' + Name: hundred cubic foot + ConversionFactor: '' + Symbol: '' + CommonCode: HH + Description: A unit of volume equal to one hundred cubic foot. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: hundred sheet + ConversionFactor: '' + Symbol: '' + CommonCode: HI + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: hundred international unit + ConversionFactor: '' + Symbol: '' + CommonCode: HIU + Description: A unit of count defining the number of international units in multiples + of 100. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: metric horse power + ConversionFactor: 735,498 75 W + Symbol: metric hp + CommonCode: HJ + Description: '' + conversion: + factor: 735.49875 + base_units: + - D46 + - P14 + - WTT +- Status: X + LevelAndCategory: '3.8' + Name: hundred kilogram + ConversionFactor: '' + Symbol: '' + CommonCode: HK + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: hundred kilogram, net mass + ConversionFactor: '' + Symbol: '' + CommonCode: HKM + Description: A unit of mass defining the number of hundred kilograms of a product, + after deductions. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: hundred foot (linear) + ConversionFactor: '' + Symbol: '' + CommonCode: HL + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: hectolitre + ConversionFactor: 10⁻¹ m³ + Symbol: hl + CommonCode: HLT + Description: '' + conversion: + factor: 0.1 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: mile per hour (statute mile) + ConversionFactor: 0,447 04 m/s + Symbol: mile/h + CommonCode: HM + Description: '' + conversion: + factor: 0.44704 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '3.8' + Name: million cubic metre + ConversionFactor: '' + Symbol: Mm³ + CommonCode: HMQ + Description: A unit of volume equal to one million cubic metres. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: hectometre + ConversionFactor: 10² m + Symbol: hm + CommonCode: HMT + Description: '' + conversion: + factor: 100.0 + base_units: + - MTR +- Status: D + LevelAndCategory: '2' + Name: conventional millimetre of mercury + ConversionFactor: 133,322 4 Pa + Symbol: mm Hg + CommonCode: HN + Description: '' + conversion: + factor: 133.3224 + base_units: + - C55 + - PAL +- Status: X + LevelAndCategory: '3.8' + Name: hundred troy ounce + ConversionFactor: '' + Symbol: '' + CommonCode: HO + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: conventional millimetre of water + ConversionFactor: 9,806 65 Pa + Symbol: mm H₂O + CommonCode: HP + Description: '' + conversion: + factor: 9.80665 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.1' + Name: hectolitre of pure alcohol + ConversionFactor: '' + Symbol: '' + CommonCode: HPA + Description: A unit of volume equal to one hundred litres of pure alcohol. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: hundred square foot + ConversionFactor: '' + Symbol: '' + CommonCode: HS + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: half hour + ConversionFactor: '' + Symbol: '' + CommonCode: HT + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: hertz + ConversionFactor: Hz + Symbol: Hz + CommonCode: HTZ + Description: '' + conversion: + factor: 1.0 + base_units: + - HTZ +- Status: '' + LevelAndCategory: '1' + Name: hour + ConversionFactor: 3 600 s + Symbol: h + CommonCode: HUR + Description: '' + conversion: + factor: 3600.0 + base_units: + - H04 + - SEC +- Status: X + LevelAndCategory: '3.8' + Name: hundred yard + ConversionFactor: '' + Symbol: '' + CommonCode: HY + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: inch pound (pound inch) + ConversionFactor: 1,152 12 x 10⁻² kg x m + Symbol: in·lb + CommonCode: IA + Description: '' + conversion: + factor: 0.0115212 + base_units: + - M94 +- Status: X + LevelAndCategory: '3.9' + Name: count per inch + ConversionFactor: '' + Symbol: '' + CommonCode: IC + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: person + ConversionFactor: '' + Symbol: '' + CommonCode: IE + Description: A unit of count defining the number of persons. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: inches of water + ConversionFactor: '' + Symbol: '' + CommonCode: IF + Description: Use inch of water (common code F78) + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: column inch + ConversionFactor: '' + Symbol: '' + CommonCode: II + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: inch per minute + ConversionFactor: '' + Symbol: '' + CommonCode: IL + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: impression + ConversionFactor: '' + Symbol: '' + CommonCode: IM + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: inch + ConversionFactor: 25,4 x 10⁻³ m + Symbol: in + CommonCode: INH + Description: '' + conversion: + factor: 0.0254 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: square inch + ConversionFactor: 6,451 6 x 10⁻⁴ m² + Symbol: in² + CommonCode: INK + Description: '' + conversion: + factor: 0.00064516 + base_units: + - MTK +- Status: '' + LevelAndCategory: '2' + Name: cubic inch + ConversionFactor: 16,387 064 x 10⁻⁶ m³ + Symbol: in³ + CommonCode: INQ + Description: 'Synonym: inch cubed' + conversion: + factor: 1.6387064e-05 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.9' + Name: insurance policy + ConversionFactor: '' + Symbol: '' + CommonCode: IP + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: international sugar degree + ConversionFactor: '' + Symbol: '' + CommonCode: ISD + Description: A unit of measure defining the sugar content of a solution, expressed + in degrees. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: count per centimetre + ConversionFactor: '' + Symbol: '' + CommonCode: IT + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: inch per second + ConversionFactor: 0,025 4 m/s + Symbol: in/s + CommonCode: IU + Description: '' + conversion: + factor: 0.0254 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '3.7' + Name: international unit per gram + ConversionFactor: '' + Symbol: '' + CommonCode: IUG + Description: A unit of count defining the number of international units per gram. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: inch per second squared + ConversionFactor: 0,025 4 m/s² + Symbol: in/s² + CommonCode: IV + Description: '' + conversion: + factor: 0.0254 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per millimetre + ConversionFactor: 10 m⁻¹ + Symbol: "%/mm" + CommonCode: J10 + Description: A unit of proportion, equal to 0.01, in relation to a millimetre. + conversion: + factor: 10.0 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '3.7' + Name: per mille per psi + ConversionFactor: 1,450 377 x 10⁻⁷ Pa⁻¹ + Symbol: "‰/psi" + CommonCode: J12 + Description: A unit of pressure equal to one thousandth of a psi (pound-force per + square inch). + conversion: + factor: 1.450377e-07 + base_units: + - C96 +- Status: '' + LevelAndCategory: '3.5' + Name: degree API + ConversionFactor: '' + Symbol: "°API" + CommonCode: J13 + Description: 'A unit of relative density as a measure of how heavy or light a petroleum + liquid is compared to water (API: American Petroleum Institute).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: degree Baume (origin scale) + ConversionFactor: '' + Symbol: "°Bé" + CommonCode: J14 + Description: A traditional unit of relative density for liquids. Named after Antoine + Baumé. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: degree Baume (US heavy) + ConversionFactor: '' + Symbol: "°Bé (US heavy)" + CommonCode: J15 + Description: A unit of relative density for liquids heavier than water. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: degree Baume (US light) + ConversionFactor: '' + Symbol: "°Bé (US light)" + CommonCode: J16 + Description: A unit of relative density for liquids lighter than water. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: degree Balling + ConversionFactor: '' + Symbol: "°Balling" + CommonCode: J17 + Description: A unit of density as a measure of sugar content, especially of beer + wort. Named after Karl Balling. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: degree Brix + ConversionFactor: '' + Symbol: "°Bx" + CommonCode: J18 + Description: A unit of proportion used in measuring the dissolved sugar-to-water + mass ratio of a liquid. Named after Adolf Brix. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit hour square foot per British thermal unit (thermochemical) + ConversionFactor: 0,176 228 m² x K/W + Symbol: "°F·h·ft²/Btuth" + CommonCode: J19 + Description: '' + conversion: + factor: 0.176228 + base_units: + - D19 +- Status: '' + LevelAndCategory: '1' + Name: joule per kilogram + ConversionFactor: J/kg + Symbol: J/kg + CommonCode: J2 + Description: '' + conversion: + factor: 1.0 + base_units: + - J2 +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit per kelvin + ConversionFactor: 0,555 555 6 + Symbol: "°F/K" + CommonCode: J20 + Description: '' + conversion: + factor: 0.5555556 +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit per bar + ConversionFactor: 0,555 555 6 x 10⁻⁵ K/Pa + Symbol: "°F/bar" + CommonCode: J21 + Description: '' + conversion: + factor: 5.555556e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit hour square foot per British thermal unit (international + table) + ConversionFactor: 0,176 110 2 m² x K/W + Symbol: "°F·h·ft²/BtuIT" + CommonCode: J22 + Description: '' + conversion: + factor: 0.1761102 + base_units: + - D19 +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit per hour + ConversionFactor: 1,543 210 x 10⁻⁴ K/s + Symbol: "°F/h" + CommonCode: J23 + Description: '' + conversion: + factor: 0.000154321 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit per minute + ConversionFactor: 9,259 259 x 10⁻³ K/s + Symbol: "°F/min" + CommonCode: J24 + Description: '' + conversion: + factor: 0.009259259 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: degree Fahrenheit per second + ConversionFactor: 0,555 555 6 K/s + Symbol: "°F/s" + CommonCode: J25 + Description: '' + conversion: + factor: 0.5555556 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: reciprocal degree Fahrenheit + ConversionFactor: 1,8 1/K + Symbol: 1/°F + CommonCode: J26 + Description: '' + conversion: + factor: 1.81 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: degree Oechsle + ConversionFactor: '' + Symbol: "°Oechsle" + CommonCode: J27 + Description: A unit of density as a measure of sugar content of must, the unfermented + liqueur from which wine is made. Named after Ferdinand Oechsle. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: degree Rankine per hour + ConversionFactor: 1,543 210 x 10⁻⁴ K/s + Symbol: "°R/h" + CommonCode: J28 + Description: '' + conversion: + factor: 0.000154321 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: degree Rankine per minute + ConversionFactor: 9,259 259 x 10⁻³ K/s + Symbol: "°R/min" + CommonCode: J29 + Description: '' + conversion: + factor: 0.009259259 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: degree Rankine per second + ConversionFactor: 0,555 555 6 K/s + Symbol: "°R/s" + CommonCode: J30 + Description: '' + conversion: + factor: 0.5555556 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: degree Twaddell + ConversionFactor: '' + Symbol: "°Tw" + CommonCode: J31 + Description: A unit of density for liquids that are heavier than water. 1 degree + Twaddle represents a difference in specific gravity of 0.005. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: micropoise + ConversionFactor: 10⁻⁶ Pa x s + Symbol: µP + CommonCode: J32 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: 1S + Name: microgram per kilogram + ConversionFactor: 10⁻⁹ + Symbol: µg/kg + CommonCode: J33 + Description: '' + conversion: + factor: 1.0e-09 +- Status: '' + LevelAndCategory: '2' + Name: microgram per cubic metre kelvin + ConversionFactor: 10⁻⁹ (kg/m³)/K + Symbol: "(µg/m³)/K" + CommonCode: J34 + Description: '' + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: microgram per cubic metre bar + ConversionFactor: 10⁻¹⁴ (kg/m³)/Pa + Symbol: "(µg/m³)/bar" + CommonCode: J35 + Description: '' + conversion: + factor: 1.0e-14 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: microlitre per litre + ConversionFactor: 10⁻⁶ + Symbol: µl/l + CommonCode: J36 + Description: '' + conversion: + factor: 1.0e-06 +- Status: '' + LevelAndCategory: '3.6' + Name: baud + ConversionFactor: '' + Symbol: Bd + CommonCode: J38 + Description: A unit of signal transmission speed equal to one signalling event per + second. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (mean) + ConversionFactor: 1,055 87 x 10³ J + Symbol: Btu + CommonCode: J39 + Description: '' + conversion: + factor: 1055.87 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) foot per hour square foot degree + Fahrenheit + ConversionFactor: 1,730 735 W/(m x K) + Symbol: BtuIT·ft/(h·ft²·°F) + CommonCode: J40 + Description: '' + conversion: + factor: 1.730735 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) inch per hour square foot degree + Fahrenheit + ConversionFactor: 0,144 227 9 W/(m x K) + Symbol: BtuIT·in/(h·ft²·°F) + CommonCode: J41 + Description: '' + conversion: + factor: 0.1442279 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) inch per second square foot degree + Fahrenheit + ConversionFactor: 5,192 204 x 10² W/(m x K) + Symbol: BtuIT·in/(s·ft²·°F) + CommonCode: J42 + Description: '' + conversion: + factor: 519.2204 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per pound degree Fahrenheit + ConversionFactor: 4,186 8 x 10³ J/(kg x K) + Symbol: BtuIT/(lb·°F) + CommonCode: J43 + Description: '' + conversion: + factor: 4186.8 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per minute + ConversionFactor: 17,584 266 W + Symbol: BtuIT/min + CommonCode: J44 + Description: '' + conversion: + factor: 17.584266 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per second + ConversionFactor: 1,055 056 x 10³ W + Symbol: BtuIT/s + CommonCode: J45 + Description: '' + conversion: + factor: 1055.056 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) foot per hour square foot degree Fahrenheit + ConversionFactor: 1,729 577 W/(m x K) + Symbol: Btuth·ft/(h·ft²·°F) + CommonCode: J46 + Description: '' + conversion: + factor: 1.729577 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per hour + ConversionFactor: 0,292 875 1 W + Symbol: Btuth/h + CommonCode: J47 + Description: '' + conversion: + factor: 0.2928751 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) inch per hour square foot degree Fahrenheit + ConversionFactor: 0,144 131 4 W/(m x K) + Symbol: Btuth·in/(h·ft²·°F) + CommonCode: J48 + Description: '' + conversion: + factor: 0.1441314 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) inch per second square foot degree Fahrenheit + ConversionFactor: 5,188 732 x 10² W/(m x K) + Symbol: Btuth·in/(s·ft²·°F) + CommonCode: J49 + Description: '' + conversion: + factor: 518.8732 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per pound degree Fahrenheit + ConversionFactor: 4,184 x 10³ J/(kg x K) + Symbol: Btuth/(lb·°F) + CommonCode: J50 + Description: '' + conversion: + factor: 4184.0 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per minute + ConversionFactor: 17,572 50 W + Symbol: Btuth/min + CommonCode: J51 + Description: '' + conversion: + factor: 17.5725 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per second + ConversionFactor: 1,054 350 x 10³ W + Symbol: Btuth/s + CommonCode: J52 + Description: '' + conversion: + factor: 1054.35 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: coulomb square metre per kilogram + ConversionFactor: C x m²/kg + Symbol: C·m²/kg + CommonCode: J53 + Description: '' + conversion: + factor: 1.0 + base_units: + - J53 +- Status: '' + LevelAndCategory: '3.6' + Name: megabaud + ConversionFactor: 10⁶ Bd + Symbol: MBd + CommonCode: J54 + Description: A unit of signal transmission speed equal to 10⁶ (1000000) signaling + events per second. + conversion: + factor: 1000000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: watt second + ConversionFactor: W x s + Symbol: W·s + CommonCode: J55 + Description: '' + conversion: + factor: 1.0 + base_units: + - J55 +- Status: '' + LevelAndCategory: '2' + Name: bar per bar + ConversionFactor: '1' + Symbol: bar/bar + CommonCode: J56 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: barrel (UK petroleum) + ConversionFactor: 0,159 113 15 m³ + Symbol: bbl (UK liq.) + CommonCode: J57 + Description: '' + conversion: + factor: 0.15911315 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: barrel (UK petroleum) per minute + ConversionFactor: 2,651 886 m³/s + Symbol: bbl (UK liq.)/min + CommonCode: J58 + Description: '' + conversion: + factor: 2.651886 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: barrel (UK petroleum) per day + ConversionFactor: 1,841 587 4 x 10⁻⁶ m³/s + Symbol: bbl (UK liq.)/d + CommonCode: J59 + Description: '' + conversion: + factor: 1.8415874e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: barrel (UK petroleum) per hour + ConversionFactor: 4,419 810 x 10⁻⁵ m³/s + Symbol: bbl (UK liq.)/h + CommonCode: J60 + Description: '' + conversion: + factor: 4.41981e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: barrel (UK petroleum) per second + ConversionFactor: 0,159 113 15 m³/s + Symbol: bbl (UK liq.)/s + CommonCode: J61 + Description: '' + conversion: + factor: 0.15911315 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: barrel (US petroleum) per hour + ConversionFactor: 4,416 314 x 10⁻⁵ m³/s + Symbol: bbl (US)/h + CommonCode: J62 + Description: '' + conversion: + factor: 4.416314e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: barrel (US petroleum) per second + ConversionFactor: 0,158 987 3 m³/s + Symbol: bbl (US)/s + CommonCode: J63 + Description: '' + conversion: + factor: 0.1589873 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (UK) per day + ConversionFactor: 4,209 343 x 10⁻⁷ m³/s + Symbol: bu (UK)/d + CommonCode: J64 + Description: '' + conversion: + factor: 4.209343e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (UK) per hour + ConversionFactor: 1,010 242 x 10⁻⁵ m³/s + Symbol: bu (UK)/h + CommonCode: J65 + Description: '' + conversion: + factor: 1.010242e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (UK) per minute + ConversionFactor: 6,061 453 x 10⁻⁴ m³/s + Symbol: bu (UK)/min + CommonCode: J66 + Description: '' + conversion: + factor: 0.0006061453 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (UK) per second + ConversionFactor: 3,636 872 x 10⁻² m³/s + Symbol: bu (UK)/s + CommonCode: J67 + Description: '' + conversion: + factor: 0.03636872 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (US dry) per day + ConversionFactor: 4,078 596 x 10⁻⁷ m³/s + Symbol: bu (US dry)/d + CommonCode: J68 + Description: '' + conversion: + factor: 4.078596e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (US dry) per hour + ConversionFactor: 9,788 631 x 10⁻⁶ m³/s + Symbol: bu (US dry)/h + CommonCode: J69 + Description: '' + conversion: + factor: 9.788631e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (US dry) per minute + ConversionFactor: 5,873 178 x 10⁻⁴ m³/s + Symbol: bu (US dry)/min + CommonCode: J70 + Description: '' + conversion: + factor: 0.0005873178 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: bushel (US dry) per second + ConversionFactor: 3,523 907 x 10⁻² m³/s + Symbol: bu (US dry)/s + CommonCode: J71 + Description: '' + conversion: + factor: 0.03523907 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1S + Name: centinewton metre + ConversionFactor: 10⁻² N x m + Symbol: cN·m + CommonCode: J72 + Description: '' + conversion: + factor: 0.01 + base_units: + - NU +- Status: '' + LevelAndCategory: '2' + Name: centipoise per kelvin + ConversionFactor: 10⁻³ Pa x s/K + Symbol: cP/K + CommonCode: J73 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: centipoise per bar + ConversionFactor: 10⁻⁸ s + Symbol: cP/bar + CommonCode: J74 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '2' + Name: calorie (mean) + ConversionFactor: 4,190 02 J + Symbol: cal + CommonCode: J75 + Description: '' + conversion: + factor: 4.19002 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: calorie (international table) per gram degree Celsius + ConversionFactor: 4,186 8 x 10³ J/(kg x K) + Symbol: calIT/(g·°C) + CommonCode: J76 + Description: '' + conversion: + factor: 4186.8 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: calorie (thermochemical) per centimetre second degree Celsius + ConversionFactor: 4,184 x 10² W/(m x K) + Symbol: calth/(cm·s·°C) + CommonCode: J78 + Description: '' + conversion: + factor: 418.4 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2' + Name: calorie (thermochemical) per gram degree Celsius + ConversionFactor: 4,184 x 10³ J/(kg x K) + Symbol: calth/(g·°C) + CommonCode: J79 + Description: '' + conversion: + factor: 4184.0 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: calorie (thermochemical) per minute + ConversionFactor: 6,973 333 x 10⁻² W + Symbol: calth/min + CommonCode: J81 + Description: '' + conversion: + factor: 0.06973333 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: calorie (thermochemical) per second + ConversionFactor: 4,184 W + Symbol: calth/s + CommonCode: J82 + Description: '' + conversion: + factor: 4.184 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: clo + ConversionFactor: 0,155 m² x K/W + Symbol: clo + CommonCode: J83 + Description: '' + conversion: + factor: 0.155 + base_units: + - D19 +- Status: '' + LevelAndCategory: '2' + Name: centimetre per second kelvin + ConversionFactor: 10⁻² (m/s)/K + Symbol: "(cm/s)/K" + CommonCode: J84 + Description: '' + conversion: + factor: 0.01 + base_units: + - L12 +- Status: '' + LevelAndCategory: '2' + Name: centimetre per second bar + ConversionFactor: 10⁻⁷ (m/s)/Pa + Symbol: "(cm/s)/bar" + CommonCode: J85 + Description: '' + conversion: + factor: 1.0e-07 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: cubic centimetre per cubic metre + ConversionFactor: 10⁻⁶ + Symbol: cm³/m³ + CommonCode: J87 + Description: '' + conversion: + factor: 1.0e-06 +- Status: D + LevelAndCategory: '2' + Name: centimetre of mercury + ConversionFactor: 1,333 224 x 10³ Pa + Symbol: cm Hg + CommonCode: J89 + Description: '' + conversion: + factor: 1333.224 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre per day + ConversionFactor: 1,157 41 x 10⁻⁸ m³/s + Symbol: dm³/d + CommonCode: J90 + Description: '' + conversion: + factor: 1.15741e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre per cubic metre + ConversionFactor: 10⁻³ + Symbol: dm³/m³ + CommonCode: J91 + Description: '' + conversion: + factor: 0.001 +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre per minute + ConversionFactor: 1,666 67 x 10⁻⁵ m³/s + Symbol: dm³/min + CommonCode: J92 + Description: '' + conversion: + factor: 1.66667e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1S + Name: cubic decimetre per second + ConversionFactor: 10⁻³ m³/s + Symbol: dm³/s + CommonCode: J93 + Description: '' + conversion: + factor: 0.001 + base_units: + - MQS +- Status: D + LevelAndCategory: '2' + Name: dyne centimetre + ConversionFactor: 10⁻⁷ N x m + Symbol: dyn·cm + CommonCode: J94 + Description: '' + conversion: + factor: 1.0e-07 + base_units: + - NU +- Status: '' + LevelAndCategory: '2' + Name: ounce (UK fluid) per day + ConversionFactor: 3,288 549 x 10⁻¹⁰ m³/s + Symbol: fl oz (UK)/d + CommonCode: J95 + Description: '' + conversion: + factor: 3.288549e-10 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: ounce (UK fluid) per hour + ConversionFactor: 7,892 517 x 10⁻⁹ m³/s + Symbol: fl oz (UK)/h + CommonCode: J96 + Description: '' + conversion: + factor: 7.892517e-09 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: ounce (UK fluid) per minute + ConversionFactor: 4,735 51 x 10⁻⁷ m³/s + Symbol: fl oz (UK)/min + CommonCode: J97 + Description: '' + conversion: + factor: 4.73551e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: ounce (UK fluid) per second + ConversionFactor: 2,841 306 x 10⁻⁵ m³/s + Symbol: fl oz (UK)/s + CommonCode: J98 + Description: '' + conversion: + factor: 2.841306e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: ounce (US fluid) per day + ConversionFactor: 3,422 862 x 10⁻¹⁰ m³/s + Symbol: fl oz (US)/d + CommonCode: J99 + Description: '' + conversion: + factor: 3.422862e-10 + base_units: + - MQS +- Status: X + LevelAndCategory: '3.4' + Name: jumbo + ConversionFactor: '' + Symbol: '' + CommonCode: JB + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: joule per kelvin + ConversionFactor: J/K + Symbol: J/K + CommonCode: JE + Description: '' + conversion: + factor: 1.0 + base_units: + - JE +- Status: X + LevelAndCategory: '3.3' + Name: jug + ConversionFactor: '' + Symbol: '' + CommonCode: JG + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: megajoule per kilogram + ConversionFactor: 10⁶ J/kg + Symbol: MJ/kg + CommonCode: JK + Description: '' + conversion: + factor: 1000000.0 + base_units: + - J2 +- Status: '' + LevelAndCategory: 1M + Name: megajoule per cubic metre + ConversionFactor: 10⁶ J/m³ + Symbol: MJ/m³ + CommonCode: JM + Description: '' + conversion: + factor: 1000000.0 + base_units: + - B8 +- Status: '' + LevelAndCategory: '3.5' + Name: pipeline joint + ConversionFactor: '' + Symbol: '' + CommonCode: JNT + Description: A count of the number of pipeline joints. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: joint + ConversionFactor: '' + Symbol: '' + CommonCode: JO + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: joule + ConversionFactor: J + Symbol: J + CommonCode: JOU + Description: '' + conversion: + factor: 1.0 + base_units: + - JOU +- Status: '' + LevelAndCategory: '3.1' + Name: hundred metre + ConversionFactor: '' + Symbol: '' + CommonCode: JPS + Description: A unit of count defining the number of 100 metre lengths. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: jar + ConversionFactor: '' + Symbol: '' + CommonCode: JR + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: number of jewels + ConversionFactor: '' + Symbol: '' + CommonCode: JWL + Description: 'A unit of count defining the number of jewels (jewel: precious stone).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: kilowatt demand + ConversionFactor: '' + Symbol: '' + CommonCode: K1 + Description: A unit of measure defining the power load measured at predetermined + intervals. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: ounce (US fluid) per hour + ConversionFactor: 8,214 869 x 10⁻⁹ m³/s + Symbol: fl oz (US)/h + CommonCode: K10 + Description: '' + conversion: + factor: 8.214869e-09 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: ounce (US fluid) per minute + ConversionFactor: 4,928 922 x 10⁻⁷ m³/s + Symbol: fl oz (US)/min + CommonCode: K11 + Description: '' + conversion: + factor: 4.928922e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: ounce (US fluid) per second + ConversionFactor: 2,957 353 x 10⁻⁵ m³/s + Symbol: fl oz (US)/s + CommonCode: K12 + Description: '' + conversion: + factor: 2.957353e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: foot per degree Fahrenheit + ConversionFactor: 0,548 64 m/K + Symbol: ft/°F + CommonCode: K13 + Description: '' + conversion: + factor: 0.54864 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: foot per hour + ConversionFactor: 8,466 667 x 10⁻⁵m/s + Symbol: ft/h + CommonCode: K14 + Description: '' + conversion: + factor: 8.466667e-05 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: foot pound-force per hour + ConversionFactor: 3,766 161 x 10⁻⁴ W + Symbol: ft·lbf/h + CommonCode: K15 + Description: '' + conversion: + factor: 0.0003766161 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: foot pound-force per minute + ConversionFactor: 2,259 697 x 10⁻² W + Symbol: ft·lbf/min + CommonCode: K16 + Description: '' + conversion: + factor: 0.02259697 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: foot per psi + ConversionFactor: 4,420 750 x 10⁻⁵ m/Pa + Symbol: ft/psi + CommonCode: K17 + Description: '' + conversion: + factor: 4.42075e-05 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: foot per second degree Fahrenheit + ConversionFactor: 0,548 64 (m/s)/K + Symbol: "(ft/s)/°F" + CommonCode: K18 + Description: '' + conversion: + factor: 0.54864 + base_units: + - L12 +- Status: '' + LevelAndCategory: '2' + Name: foot per second psi + ConversionFactor: 4,420 750 x 10⁻⁵ (m/s)/Pa + Symbol: "(ft/s)/psi" + CommonCode: K19 + Description: '' + conversion: + factor: 4.42075e-05 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: kilovolt ampere reactive demand + ConversionFactor: '' + Symbol: '' + CommonCode: K2 + Description: A unit of measure defining the reactive power demand equal to one kilovolt + ampere of reactive power. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: reciprocal cubic foot + ConversionFactor: 35,314 66 m⁻³ + Symbol: 1/ft³ + CommonCode: K20 + Description: '' + conversion: + factor: 35.31466 + base_units: + - C86 +- Status: '' + LevelAndCategory: '2' + Name: cubic foot per degree Fahrenheit + ConversionFactor: 5,097 033 x 10⁻² m³/K + Symbol: ft³/°F + CommonCode: K21 + Description: '' + conversion: + factor: 0.05097033 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: cubic foot per day + ConversionFactor: 3,277 413 x 10⁻⁷ m³/s + Symbol: ft³/d + CommonCode: K22 + Description: '' + conversion: + factor: 3.277413e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: cubic foot per psi + ConversionFactor: 4,107 012 x 10⁻⁶ m³/Pa + Symbol: ft³/psi + CommonCode: K23 + Description: '' + conversion: + factor: 4.107012e-06 + base_units: [] +- Status: D + LevelAndCategory: '2' + Name: foot of water + ConversionFactor: 2,989 067 x 10³ Pa + Symbol: ft H₂O + CommonCode: K24 + Description: '' + conversion: + factor: 2989.067 + base_units: + - C55 + - PAL +- Status: D + LevelAndCategory: '2' + Name: foot of mercury + ConversionFactor: 4,063 666 x 10⁴ Pa + Symbol: ft Hg + CommonCode: K25 + Description: '' + conversion: + factor: 40636.66 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: gallon (UK) per day + ConversionFactor: 5,261 678 x 10⁻⁸ m³/s + Symbol: gal (UK)/d + CommonCode: K26 + Description: '' + conversion: + factor: 5.261678e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gallon (UK) per hour + ConversionFactor: 1,262 803 x 10⁻⁶ m³/s + Symbol: gal (UK)/h + CommonCode: K27 + Description: '' + conversion: + factor: 1.262803e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gallon (UK) per second + ConversionFactor: 4,546 09 x 10⁻³ m³/s + Symbol: gal (UK)/s + CommonCode: K28 + Description: '' + conversion: + factor: 0.00454609 + base_units: + - MQS +- Status: '' + LevelAndCategory: '3.5' + Name: kilovolt ampere reactive hour + ConversionFactor: '' + Symbol: kvar·h + CommonCode: K3 + Description: A unit of measure defining the accumulated reactive energy equal to + one kilovolt ampere of reactive power per hour. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: gallon (US liquid) per second + ConversionFactor: 3,785 412 x 10⁻³ m³/s + Symbol: gal (US liq.)/s + CommonCode: K30 + Description: '' + conversion: + factor: 0.003785412 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gram-force per square centimetre + ConversionFactor: 98,066 5 Pa + Symbol: gf/cm² + CommonCode: K31 + Description: '' + conversion: + factor: 98.0665 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: gill (UK) per day + ConversionFactor: 1,644 274 x 10⁻⁵ m³/s + Symbol: gi (UK)/d + CommonCode: K32 + Description: '' + conversion: + factor: 1.644274e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (UK) per hour + ConversionFactor: 3,946 258 x 10⁻⁸ m³/s + Symbol: gi (UK)/h + CommonCode: K33 + Description: '' + conversion: + factor: 3.946258e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (UK) per minute + ConversionFactor: 0,023 677 55 m³/s + Symbol: gi (UK)/min + CommonCode: K34 + Description: '' + conversion: + factor: 0.02367755 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (UK) per second + ConversionFactor: 1,420 653 x 10⁻⁴ m³/s + Symbol: gi (UK)/s + CommonCode: K35 + Description: '' + conversion: + factor: 0.0001420653 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (US) per day + ConversionFactor: 1,369 145 x 10⁻⁹ m³/s + Symbol: gi (US)/d + CommonCode: K36 + Description: '' + conversion: + factor: 1.369145e-09 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (US) per hour + ConversionFactor: 3,285 947 x 10⁻⁸ m³/s + Symbol: gi (US)/h + CommonCode: K37 + Description: '' + conversion: + factor: 3.285947e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (US) per minute + ConversionFactor: 1,971 568 x 10⁻⁶ m³/s + Symbol: gi (US)/min + CommonCode: K38 + Description: '' + conversion: + factor: 1.971568e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: gill (US) per second + ConversionFactor: 1,182 941 x 10⁻⁴ m³/s + Symbol: gi (US)/s + CommonCode: K39 + Description: '' + conversion: + factor: 0.0001182941 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: standard acceleration of free fall + ConversionFactor: 9,806 65 m/s² + Symbol: gn + CommonCode: K40 + Description: '' + conversion: + factor: 9.80665 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: '2' + Name: grain per gallon (US) + ConversionFactor: 1,711 806 x 10⁻² kg/m³ + Symbol: gr/gal (US) + CommonCode: K41 + Description: '' + conversion: + factor: 0.01711806 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: horsepower (boiler) + ConversionFactor: 9,809 50 x 10³ W + Symbol: boiler hp + CommonCode: K42 + Description: '' + conversion: + factor: 9809.5 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: horsepower (electric) + ConversionFactor: 746 W + Symbol: electric hp + CommonCode: K43 + Description: '' + conversion: + factor: 746.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: inch per degree Fahrenheit + ConversionFactor: 4,572 x 10⁻² m/K + Symbol: in/°F + CommonCode: K45 + Description: '' + conversion: + factor: 0.04572 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: inch per psi + ConversionFactor: 3,683 959 x 10⁻⁶ m/Pa + Symbol: in/psi + CommonCode: K46 + Description: '' + conversion: + factor: 3.683959e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: inch per second degree Fahrenheit + ConversionFactor: 4,572 x 10⁻² (m/s)/K + Symbol: "(in/s)/°F" + CommonCode: K47 + Description: '' + conversion: + factor: 0.04572 + base_units: + - L12 +- Status: '' + LevelAndCategory: '2' + Name: inch per second psi + ConversionFactor: 3,683 959 x 10⁻⁶ (m/s)/Pa + Symbol: "(in/s)/psi" + CommonCode: K48 + Description: '' + conversion: + factor: 3.683959e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: reciprocal cubic inch + ConversionFactor: 6,102 375 9 x 10⁴ m⁻³ + Symbol: 1/in³ + CommonCode: K49 + Description: '' + conversion: + factor: 61023.759 + base_units: + - C86 +- Status: D + LevelAndCategory: 1S + Name: kilovolt ampere (reactive) + ConversionFactor: 10³ V x A + Symbol: kvar + CommonCode: K5 + Description: Use kilovar (common code KVR) + conversion: + factor: 1000.0 + base_units: + - D44 +- Status: '' + LevelAndCategory: '3.6' + Name: kilobaud + ConversionFactor: 10³ Bd + Symbol: kBd + CommonCode: K50 + Description: A unit of signal transmission speed equal to 10³ (1000) signaling events + per second. + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (mean) + ConversionFactor: 4,190 02 x 10³ J + Symbol: kcal + CommonCode: K51 + Description: '' + conversion: + factor: 4190.02 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (international table) per hour metre degree Celsius + ConversionFactor: 1,163 J/(m x s x K) + Symbol: kcal/(m·h·°C) + CommonCode: K52 + Description: '' + conversion: + factor: 1.163 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (thermochemical) + ConversionFactor: 4,184 x 10³ J + Symbol: kcalth + CommonCode: K53 + Description: '' + conversion: + factor: 4184.0 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (thermochemical) per minute + ConversionFactor: 69,733 33 W + Symbol: kcalth/min + CommonCode: K54 + Description: '' + conversion: + factor: 69.73333 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (thermochemical) per second + ConversionFactor: 4,184 x 10³ W + Symbol: kcalth/s + CommonCode: K55 + Description: '' + conversion: + factor: 4184.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1S + Name: kilomole per hour + ConversionFactor: 2,777 78 x 10⁻¹ mol/s + Symbol: kmol/h + CommonCode: K58 + Description: '' + conversion: + factor: 0.277778 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: kilomole per cubic metre kelvin + ConversionFactor: 10³ (mol/m³)/K + Symbol: "(kmol/m³)/K" + CommonCode: K59 + Description: '' + conversion: + factor: 1000.0 + base_units: + - L28 +- Status: '' + LevelAndCategory: 1M + Name: kilolitre + ConversionFactor: m³ + Symbol: kl + CommonCode: K6 + Description: '' + conversion: + factor: 1.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: kilomole per cubic metre bar + ConversionFactor: 10⁻² (mol/m³)/Pa + Symbol: "(kmol/m³)/bar" + CommonCode: K60 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: kilomole per minute + ConversionFactor: 16,666 7 mol/s + Symbol: kmol/min + CommonCode: K61 + Description: '' + conversion: + factor: 16.6667 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: litre per litre + ConversionFactor: '1' + Symbol: l/l + CommonCode: K62 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: reciprocal litre + ConversionFactor: 10³ m⁻³ + Symbol: 1/l + CommonCode: K63 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C86 +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per degree Fahrenheit + ConversionFactor: 0,816 466 3 kg/K + Symbol: lb/°F + CommonCode: K64 + Description: '' + conversion: + factor: 0.8164663 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) square foot + ConversionFactor: 4,214 011 x 10⁻² kg x m² + Symbol: lb·ft² + CommonCode: K65 + Description: '' + conversion: + factor: 0.04214011 + base_units: + - B32 +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per day + ConversionFactor: 5,249 912 x 10⁻⁶ kg/s + Symbol: lb/d + CommonCode: K66 + Description: '' + conversion: + factor: 5.249912e-06 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: pound per foot hour + ConversionFactor: 4,133 789 x 10⁻⁴ Pa x s + Symbol: lb/(ft·h) + CommonCode: K67 + Description: '' + conversion: + factor: 0.0004133789 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2' + Name: pound per foot second + ConversionFactor: 1,488 164 Pa x s + Symbol: lb/(ft·s) + CommonCode: K68 + Description: '' + conversion: + factor: 1.488164 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per cubic foot degree Fahrenheit + ConversionFactor: 28,833 23 (kg/m³)/K + Symbol: "(lb/ft³)/°F" + CommonCode: K69 + Description: '' + conversion: + factor: 28.83323 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per cubic foot psi + ConversionFactor: 2,323 282 x 10⁻³ + Symbol: "(lb/ft³)/psi" + CommonCode: K70 + Description: '' + conversion: + factor: 0.002323282 +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per gallon (UK) + ConversionFactor: 99,776 37 kg/m³ + Symbol: lb/gal (UK) + CommonCode: K71 + Description: '' + conversion: + factor: 99.77637 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per hour degree Fahrenheit + ConversionFactor: 2,267 962 x 10⁻⁴ (kg/s)/K + Symbol: "(lb/h)/°F" + CommonCode: K73 + Description: '' + conversion: + factor: 0.0002267962 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per hour psi + ConversionFactor: 1,827 445 x 10⁻⁸ (kg/s)/Pa + Symbol: "(lb/h)/psi" + CommonCode: K74 + Description: '' + conversion: + factor: 1.827445e-08 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per cubic inch degree Fahrenheit + ConversionFactor: 4,982 384 x 10⁴ (kg/m³)/K + Symbol: "(lb/in³)/°F" + CommonCode: K75 + Description: '' + conversion: + factor: 49823.84 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per cubic inch psi + ConversionFactor: 4,014 632 (kg/m³)/Pa + Symbol: "(lb/in³)/psi" + CommonCode: K76 + Description: '' + conversion: + factor: 4.014632 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per psi + ConversionFactor: 6,578 802 x 10⁻⁵ kg/Pa + Symbol: lb/psi + CommonCode: K77 + Description: '' + conversion: + factor: 6.578802e-05 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per minute + ConversionFactor: 7,559 873 x 10⁻³ kg/s + Symbol: lb/min + CommonCode: K78 + Description: '' + conversion: + factor: 0.007559873 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per minute degree Fahrenheit + ConversionFactor: 1,360 777 x 10⁻² (kg/s)/K + Symbol: lb/(min·°F) + CommonCode: K79 + Description: '' + conversion: + factor: 0.01360777 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per minute psi + ConversionFactor: 1,096 467 x 10⁻⁶ (kg/s)/Pa + Symbol: "(lb/min)/psi" + CommonCode: K80 + Description: '' + conversion: + factor: 1.096467e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per second + ConversionFactor: 0,453 592 4 kg/s + Symbol: lb/s + CommonCode: K81 + Description: '' + conversion: + factor: 0.4535924 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per second degree Fahrenheit + ConversionFactor: 0,816 466 3 (kg/s)/K + Symbol: "(lb/s)/°F" + CommonCode: K82 + Description: '' + conversion: + factor: 0.8164663 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound (avoirdupois) per second psi + ConversionFactor: 6,578 802 x 10⁻⁵ (kg/s)/Pa + Symbol: "(lb/s)/psi" + CommonCode: K83 + Description: '' + conversion: + factor: 6.578802e-05 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound per cubic yard + ConversionFactor: 0,593 276 4 kg/m³ + Symbol: lb/yd³ + CommonCode: K84 + Description: '' + conversion: + factor: 0.5932764 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: pound-force per square foot + ConversionFactor: 47,880 26 Pa + Symbol: lbf/ft² + CommonCode: K85 + Description: '' + conversion: + factor: 47.88026 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: pound-force per square inch degree Fahrenheit + ConversionFactor: 1,241 056 x 10⁴ Pa/K + Symbol: psi/°F + CommonCode: K86 + Description: '' + conversion: + factor: 12410.56 + base_units: + - C64 +- Status: '' + LevelAndCategory: '2' + Name: psi cubic inch per second + ConversionFactor: 0,112 985 Pa x m³/s + Symbol: psi·in³/s + CommonCode: K87 + Description: '' + conversion: + factor: 0.112985 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: psi litre per second + ConversionFactor: 6,894 757 Pa x m³/s + Symbol: psi·l/s + CommonCode: K88 + Description: '' + conversion: + factor: 6.894757 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: psi cubic metre per second + ConversionFactor: 6,894 757 x 10³ Pa x m³/s + Symbol: psi·m³/s + CommonCode: K89 + Description: '' + conversion: + factor: 6894.757 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: psi cubic yard per second + ConversionFactor: 5,271 420 x 10³ Pa x m³/s + Symbol: psi·yd³/s + CommonCode: K90 + Description: '' + conversion: + factor: 5271.42 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound-force second per square foot + ConversionFactor: 47,880 26 Pa x s + Symbol: lbf·s/ft² + CommonCode: K91 + Description: '' + conversion: + factor: 47.88026 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2' + Name: pound-force second per square inch + ConversionFactor: 6,894 757 x 10³ Pa x s + Symbol: lbf·s/in² + CommonCode: K92 + Description: '' + conversion: + factor: 6894.757 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2' + Name: reciprocal psi + ConversionFactor: 1,450 377 x 10⁻⁴ Pa⁻¹ + Symbol: 1/psi + CommonCode: K93 + Description: '' + conversion: + factor: 0.0001450377 + base_units: + - C96 +- Status: '' + LevelAndCategory: '2' + Name: quart (UK liquid) per day + ConversionFactor: 1,315 420 x 10⁻⁸ m³/s + Symbol: qt (UK liq.)/d + CommonCode: K94 + Description: '' + conversion: + factor: 1.31542e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: quart (UK liquid) per hour + ConversionFactor: 3,157 008 x 10⁻⁷ m³/s + Symbol: qt (UK liq.)/h + CommonCode: K95 + Description: '' + conversion: + factor: 3.157008e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: quart (UK liquid) per minute + ConversionFactor: 1,894 205 x 10⁻⁵ m³/s + Symbol: qt (UK liq.)/min + CommonCode: K96 + Description: '' + conversion: + factor: 1.894205e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: quart (UK liquid) per second + ConversionFactor: 1,136 523 x 10⁻³ m³/s + Symbol: qt (UK liq.)/s + CommonCode: K97 + Description: '' + conversion: + factor: 0.001136523 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: quart (US liquid) per day + ConversionFactor: 1,095 316 x 10⁻⁸ m³/s + Symbol: qt (US liq.)/d + CommonCode: K98 + Description: '' + conversion: + factor: 1.095316e-08 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: quart (US liquid) per hour + ConversionFactor: 2,628 758 x 10⁻⁷ m³/s + Symbol: qt (US liq.)/h + CommonCode: K99 + Description: '' + conversion: + factor: 2.628758e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '3.9' + Name: cake + ConversionFactor: '' + Symbol: '' + CommonCode: KA + Description: 'A unit of count defining the number of cakes (cake: object shaped + into a flat, compact mass).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: katal + ConversionFactor: s⁻¹ x mol + Symbol: kat + CommonCode: KAT + Description: A unit of catalytic activity defining the catalytic activity of enzymes + and other catalysts. + conversion: + factor: 1.0 + base_units: + - KAT +- Status: '' + LevelAndCategory: '3.9' + Name: kilocharacter + ConversionFactor: '' + Symbol: '' + CommonCode: KB + Description: A unit of information equal to 10³ (1000) characters. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: kilobar + ConversionFactor: 10⁸ Pa + Symbol: kbar + CommonCode: KBA + Description: '' + conversion: + factor: 100000000.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of choline chloride + ConversionFactor: '' + Symbol: kg C₅ H₁₄ClNO + CommonCode: KCC + Description: A unit of mass equal to one thousand grams of choline chloride. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: kilogram decimal + ConversionFactor: '' + Symbol: '' + CommonCode: KD + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram drained net weight + ConversionFactor: '' + Symbol: kg/net eda + CommonCode: KDW + Description: A unit of mass defining the net number of kilograms of a product, disregarding + the liquid content of the product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: kelvin + ConversionFactor: K + Symbol: K + CommonCode: KEL + Description: 'Refer ISO 80000-5 (Quantities and units — Part 5: Thermodynamics)' + conversion: + factor: 1.0 + base_units: + - CEL + - KEL +- Status: X + LevelAndCategory: '3.9' + Name: kilopacket + ConversionFactor: '' + Symbol: '' + CommonCode: KF + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: keg + ConversionFactor: '' + Symbol: '' + CommonCode: KG + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: kilogram + ConversionFactor: kg + Symbol: kg + CommonCode: KGM + Description: A unit of mass equal to one thousand grams. + conversion: + factor: 1.0 + base_units: + - KGM +- Status: '' + LevelAndCategory: '1' + Name: kilogram per second + ConversionFactor: kg/s + Symbol: kg/s + CommonCode: KGS + Description: '' + conversion: + factor: 1.0 + base_units: + - KGS +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of hydrogen peroxide + ConversionFactor: '' + Symbol: kg H₂O₂ + CommonCode: KHY + Description: A unit of mass equal to one thousand grams of hydrogen peroxide. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilohertz + ConversionFactor: 10³ Hz + Symbol: kHz + CommonCode: KHZ + Description: '' + conversion: + factor: 1000.0 + base_units: + - HTZ +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram per millimetre width + ConversionFactor: 10³ kg/m + Symbol: '' + CommonCode: KI + Description: '' + conversion: + factor: 1000.0 + base_units: + - KL +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram, including container + ConversionFactor: '' + Symbol: '' + CommonCode: KIC + Description: A unit of mass defining the number of kilograms of a product, including + its container. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram, including inner packaging + ConversionFactor: '' + Symbol: '' + CommonCode: KIP + Description: A unit of mass defining the number of kilograms of a product, including + its inner packaging materials. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: kilosegment + ConversionFactor: '' + Symbol: '' + CommonCode: KJ + Description: A unit of information equal to 10³ (1000) segments. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilojoule + ConversionFactor: 10³ J + Symbol: kJ + CommonCode: KJO + Description: '' + conversion: + factor: 1000.0 + base_units: + - JOU +- Status: '' + LevelAndCategory: '1' + Name: kilogram per metre + ConversionFactor: kg/m + Symbol: kg/m + CommonCode: KL + Description: '' + conversion: + factor: 1.0 + base_units: + - KL +- Status: '' + LevelAndCategory: '3.5' + Name: lactic dry material percentage + ConversionFactor: '' + Symbol: '' + CommonCode: KLK + Description: A unit of proportion defining the percentage of dry lactic material + in a product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: kilolux + ConversionFactor: 10³ cd x sr / m² + Symbol: klx + CommonCode: KLX + Description: A unit of illuminance equal to one thousand lux. + conversion: + factor: 1000.0 + base_units: + - LUX +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of methylamine + ConversionFactor: '' + Symbol: kg met.am. + CommonCode: KMA + Description: A unit of mass equal to one thousand grams of methylamine. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilometre per hour + ConversionFactor: 0,277 778 m/s + Symbol: km/h + CommonCode: KMH + Description: '' + conversion: + factor: 0.277778 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: 1S + Name: square kilometre + ConversionFactor: 10⁶ m² + Symbol: km² + CommonCode: KMK + Description: '' + conversion: + factor: 1000000.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: '1' + Name: kilogram per cubic metre + ConversionFactor: kg/m³ + Symbol: kg/m³ + CommonCode: KMQ + Description: A unit of weight expressed in kilograms of a substance that fills a + volume of one cubic metre. + conversion: + factor: 1.0 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: 1S + Name: kilometre + ConversionFactor: 10³ m + Symbol: km + CommonCode: KMT + Description: '' + conversion: + factor: 1000.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of nitrogen + ConversionFactor: '' + Symbol: kg N + CommonCode: KNI + Description: A unit of mass equal to one thousand grams of nitrogen. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilonewton per square metre + ConversionFactor: 103pascal + Symbol: kN/m2 + CommonCode: KNM + Description: Pressure expressed in kN/m2. + conversion: + factor: 103.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram named substance + ConversionFactor: '' + Symbol: '' + CommonCode: KNS + Description: A unit of mass equal to one kilogram of a named substance. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: knot + ConversionFactor: 0,514 444 m/s + Symbol: kn + CommonCode: KNT + Description: '' + conversion: + factor: 0.514444 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '3.9' + Name: milliequivalence caustic potash per gram of product + ConversionFactor: '' + Symbol: '' + CommonCode: KO + Description: A unit of count defining the number of milligrams of potassium hydroxide + per gram of product as a measure of the concentration of potassium hydroxide in + the product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilopascal + ConversionFactor: 10³ Pa + Symbol: kPa + CommonCode: KPA + Description: '' + conversion: + factor: 1000.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of potassium hydroxide (caustic potash) + ConversionFactor: '' + Symbol: kg KOH + CommonCode: KPH + Description: A unit of mass equal to one thousand grams of potassium hydroxide (caustic + potash). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of potassium oxide + ConversionFactor: '' + Symbol: kg K₂O + CommonCode: KPO + Description: A unit of mass equal to one thousand grams of potassium oxide. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of phosphorus pentoxide (phosphoric anhydride) + ConversionFactor: '' + Symbol: '' + CommonCode: KPP + Description: A unit of mass equal to one thousand grams of phosphorus pentoxide + phosphoric anhydride. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: kiloroentgen + ConversionFactor: 2,58 x 10⁻¹ C/kg + Symbol: kR + CommonCode: KR + Description: '' + conversion: + factor: 0.258 + base_units: [] +- Status: X + LevelAndCategory: '3.8' + Name: thousand pound per square inch + ConversionFactor: '' + Symbol: '' + CommonCode: KS + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of substance 90 % dry + ConversionFactor: '' + Symbol: kg 90 % sdt + CommonCode: KSD + Description: A unit of mass equal to one thousand grams of a named substance that + is 90% dry. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of sodium hydroxide (caustic soda) + ConversionFactor: '' + Symbol: kg NaOH + CommonCode: KSH + Description: A unit of mass equal to one thousand grams of sodium hydroxide (caustic + soda). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.2' + Name: kit + ConversionFactor: '' + Symbol: '' + CommonCode: KT + Description: 'A unit of count defining the number of kits (kit: tub, barrel or pail).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: 1S + Name: kilometre + ConversionFactor: 10³ m + Symbol: km + CommonCode: KTM + Description: '' + conversion: + factor: 1000.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1M + Name: kilotonne + ConversionFactor: 10⁶ kg + Symbol: kt + CommonCode: KTN + Description: '' + conversion: + factor: 1000000.0 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of uranium + ConversionFactor: '' + Symbol: kg U + CommonCode: KUR + Description: A unit of mass equal to one thousand grams of uranium. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilovolt - ampere + ConversionFactor: 10³ V x A + Symbol: kV·A + CommonCode: KVA + Description: '' + conversion: + factor: 1000.0 + base_units: + - D44 +- Status: '' + LevelAndCategory: 1S + Name: kilovar + ConversionFactor: 10³ V x A + Symbol: kvar + CommonCode: KVR + Description: '' + conversion: + factor: 1000.0 + base_units: + - D44 +- Status: '' + LevelAndCategory: 1S + Name: kilovolt + ConversionFactor: 10³ V + Symbol: kV + CommonCode: KVT + Description: '' + conversion: + factor: 1000.0 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: 1M + Name: kilogram per millimetre + ConversionFactor: 10³ kg/m + Symbol: kg/mm + CommonCode: KW + Description: '' + conversion: + factor: 1000.0 + base_units: + - KL +- Status: '' + LevelAndCategory: 1S + Name: kilowatt hour + ConversionFactor: 3,6 x 10⁶ J + Symbol: kW·h + CommonCode: KWH + Description: '' + conversion: + factor: 3600000.0 + base_units: + - JOU +- Status: "+" + LevelAndCategory: '2' + Name: kilowatt year + ConversionFactor: '' + Symbol: kW/year + CommonCode: KWY + Description: killowatt year + conversion: + factor: 1.0 +- Status: "¦" + LevelAndCategory: '2' + Name: Kilowatt hour per normalized cubic metre + ConversionFactor: '' + Symbol: '' + CommonCode: KWN + Description: Kilowatt hour per normalized cubic metre (temperature 0°C and pressure + 1013.25 millibars ). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram of tungsten trioxide + ConversionFactor: '' + Symbol: kg WO₃ + CommonCode: KWO + Description: A unit of mass equal to one thousand grams of tungsten trioxide. + conversion: + factor: 1.0 +- Status: "¦" + LevelAndCategory: '2' + Name: Kilowatt hour per standard cubic metre + ConversionFactor: '' + Symbol: '' + CommonCode: KWS + Description: Kilowatt hour per standard cubic metre (temperature 15°C and pressure + 1013.25 millibars). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: kilowatt + ConversionFactor: 10³ W + Symbol: kW + CommonCode: KWT + Description: '' + conversion: + factor: 1000.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1M + Name: millilitre per kilogram + ConversionFactor: 10⁻⁶ m³/kg + Symbol: ml/kg + CommonCode: KX + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - A39 +- Status: '' + LevelAndCategory: '2' + Name: quart (US liquid) per minute + ConversionFactor: 1,577 255 x 10⁻⁵ m³/s + Symbol: qt (US liq.)/min + CommonCode: L10 + Description: '' + conversion: + factor: 1.577255e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: quart (US liquid) per second + ConversionFactor: 9,463 529 x 10⁻⁴ m³/s + Symbol: qt (US liq.)/s + CommonCode: L11 + Description: '' + conversion: + factor: 0.0009463529 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: metre per second kelvin + ConversionFactor: "(m/s)/K" + Symbol: "(m/s)/K" + CommonCode: L12 + Description: '' + conversion: + factor: 1.0 + base_units: + - L12 +- Status: '' + LevelAndCategory: '2' + Name: metre per second bar + ConversionFactor: 10⁻⁵ (m/s)/Pa + Symbol: "(m/s)/bar" + CommonCode: L13 + Description: '' + conversion: + factor: 1.0e-05 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: square metre hour degree Celsius per kilocalorie (international table) + ConversionFactor: 0,859 845 2 m² x s x K/J + Symbol: m²·h·°C/kcal + CommonCode: L14 + Description: '' + conversion: + factor: 0.8598452 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: millipascal second per kelvin + ConversionFactor: 10⁻³ Pa x s/K + Symbol: mPa·s/K + CommonCode: L15 + Description: '' + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: millipascal second per bar + ConversionFactor: 10⁻⁸ s + Symbol: mPa·s/bar + CommonCode: L16 + Description: '' + conversion: + factor: 1.0e-08 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '2' + Name: milligram per cubic metre kelvin + ConversionFactor: 10⁻⁶ (kg/m³)/K + Symbol: "(mg/m³)/K" + CommonCode: L17 + Description: '' + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: milligram per cubic metre bar + ConversionFactor: 10⁻¹¹ (kg/m³)/Pa + Symbol: "(mg/m³)/bar" + CommonCode: L18 + Description: '' + conversion: + factor: 1.0e-11 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: millilitre per litre + ConversionFactor: 10⁻³ + Symbol: ml/l + CommonCode: L19 + Description: '' + conversion: + factor: 0.001 +- Status: '' + LevelAndCategory: 1M + Name: litre per minute + ConversionFactor: 1,666 67 x 10⁻⁵ m³/s + Symbol: l/min + CommonCode: L2 + Description: '' + conversion: + factor: 1.66667e-05 + base_units: + - MQS +- Status: '' + LevelAndCategory: 1S + Name: reciprocal cubic millimetre + ConversionFactor: 10⁹ m⁻³ + Symbol: 1/mm³ + CommonCode: L20 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - C86 +- Status: '' + LevelAndCategory: 1S + Name: cubic millimetre per cubic metre + ConversionFactor: 10⁹ + Symbol: mm³/m³ + CommonCode: L21 + Description: '' + conversion: + factor: 1000000000.0 +- Status: '' + LevelAndCategory: 1S + Name: mole per hour + ConversionFactor: 2,777 78 x 10⁻⁴ mol/s + Symbol: mol/h + CommonCode: L23 + Description: '' + conversion: + factor: 0.000277778 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: mole per kilogram kelvin + ConversionFactor: "(mol/kg)/K" + Symbol: "(mol/kg)/K" + CommonCode: L24 + Description: '' + conversion: + factor: 1.0 + base_units: + - L24 +- Status: '' + LevelAndCategory: '2' + Name: mole per kilogram bar + ConversionFactor: 10⁻⁵ (mol/kg)/Pa + Symbol: "(mol/kg)/bar" + CommonCode: L25 + Description: '' + conversion: + factor: 1.0e-05 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: mole per litre kelvin + ConversionFactor: 10³ (mol/m³)/K + Symbol: "(mol/l)/K" + CommonCode: L26 + Description: '' + conversion: + factor: 1000.0 + base_units: + - L28 +- Status: '' + LevelAndCategory: '2' + Name: mole per litre bar + ConversionFactor: 10⁻² (mol/m³)/Pa + Symbol: "(mol/l)/bar" + CommonCode: L27 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: mole per cubic metre kelvin + ConversionFactor: "(mol/m³)/K" + Symbol: "(mol/m³)/K" + CommonCode: L28 + Description: '' + conversion: + factor: 1.0 + base_units: + - L28 +- Status: '' + LevelAndCategory: '2' + Name: mole per cubic metre bar + ConversionFactor: 10⁻⁵ (mol/m³)/Pa + Symbol: "(mol/m³)/bar" + CommonCode: L29 + Description: '' + conversion: + factor: 1.0e-05 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: mole per minute + ConversionFactor: 1,666 67 x 10⁻² mol/s + Symbol: mol/min + CommonCode: L30 + Description: '' + conversion: + factor: 0.0166667 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: milliroentgen aequivalent men + ConversionFactor: 10⁻⁵ Sv + Symbol: mrem + CommonCode: L31 + Description: '' + conversion: + factor: 1.0e-05 + base_units: [] +- Status: '' + LevelAndCategory: 1S + Name: nanogram per kilogram + ConversionFactor: 10⁻¹² + Symbol: ng/kg + CommonCode: L32 + Description: '' + conversion: + factor: 1.0e-12 +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per day + ConversionFactor: 3,281 194 x 10⁻⁷kg/s + Symbol: oz/d + CommonCode: L33 + Description: '' + conversion: + factor: 3.281194e-07 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per hour + ConversionFactor: 7,874 867 x 10⁻⁶ kg/s + Symbol: oz/h + CommonCode: L34 + Description: '' + conversion: + factor: 7.874867e-06 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per minute + ConversionFactor: 4,724 92 x 10⁻⁴ kg/s + Symbol: oz/min + CommonCode: L35 + Description: '' + conversion: + factor: 0.000472492 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per second + ConversionFactor: 2,834 952 x 10⁻² kg/s + Symbol: oz/s + CommonCode: L36 + Description: '' + conversion: + factor: 0.02834952 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per gallon (UK) + ConversionFactor: 6,236 023 kg/m³ + Symbol: oz/gal (UK) + CommonCode: L37 + Description: '' + conversion: + factor: 6.236023 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per gallon (US) + ConversionFactor: 7,489 152 kg/m³ + Symbol: oz/gal (US) + CommonCode: L38 + Description: '' + conversion: + factor: 7.489152 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per cubic inch + ConversionFactor: 1,729 994 x 10³ kg/m³ + Symbol: oz/in³ + CommonCode: L39 + Description: '' + conversion: + factor: 1729.994 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois)-force + ConversionFactor: 0,278 013 9 N + Symbol: ozf + CommonCode: L40 + Description: '' + conversion: + factor: 0.2780139 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois)-force inch + ConversionFactor: 7,061 552 x 10⁻³ N x m + Symbol: ozf·in + CommonCode: L41 + Description: '' + conversion: + factor: 0.007061552 + base_units: + - NU +- Status: '' + LevelAndCategory: '2' + Name: picosiemens per metre + ConversionFactor: 10⁻¹² S/m + Symbol: pS/m + CommonCode: L42 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - D10 +- Status: '' + LevelAndCategory: '2' + Name: peck (UK) + ConversionFactor: 9,092 181 x 10⁻³ m³ + Symbol: pk (UK) + CommonCode: L43 + Description: '' + conversion: + factor: 0.009092181 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: peck (UK) per day + ConversionFactor: 1,052 336 x 10⁻⁷ m³/s + Symbol: pk (UK)/d + CommonCode: L44 + Description: '' + conversion: + factor: 1.052336e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (UK) per hour + ConversionFactor: 2,525 606 x 10⁻⁶ m³/s + Symbol: pk (UK)/h + CommonCode: L45 + Description: '' + conversion: + factor: 2.525606e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (UK) per minute + ConversionFactor: 1,515 363 5 x 10⁻⁴ m³/s + Symbol: pk (UK)/min + CommonCode: L46 + Description: '' + conversion: + factor: 0.00015153635 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (UK) per second + ConversionFactor: 9,092 181 x 10⁻³ m³/s + Symbol: pk (UK)/s + CommonCode: L47 + Description: '' + conversion: + factor: 0.009092181 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (US dry) per day + ConversionFactor: 1,019 649 x 10⁻⁷ m³/s + Symbol: pk (US dry)/d + CommonCode: L48 + Description: '' + conversion: + factor: 1.019649e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (US dry) per hour + ConversionFactor: 2,447 158 x 10⁻⁶ m³/s + Symbol: pk (US dry)/h + CommonCode: L49 + Description: '' + conversion: + factor: 2.447158e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (US dry) per minute + ConversionFactor: 1,468 295 x 10⁻⁴ m³/s + Symbol: pk (US dry)/min + CommonCode: L50 + Description: '' + conversion: + factor: 0.0001468295 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: peck (US dry) per second + ConversionFactor: 8,809 768 x 10⁻³ m³/s + Symbol: pk (US dry)/s + CommonCode: L51 + Description: '' + conversion: + factor: 0.008809768 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: psi per psi + ConversionFactor: '1.0' + Symbol: psi/psi + CommonCode: L52 + Description: '' + conversion: + factor: 1.0 + base_units: + - E98 + - F02 + - H60 + - L52 + - M91 +- Status: '' + LevelAndCategory: '2' + Name: pint (UK) per day + ConversionFactor: 6,577 098 x 10⁻⁹ m³/s + Symbol: pt (UK)/d + CommonCode: L53 + Description: '' + conversion: + factor: 6.577098e-09 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (UK) per hour + ConversionFactor: 1,578 504 x 10⁻⁷ m³/s + Symbol: pt (UK)/h + CommonCode: L54 + Description: '' + conversion: + factor: 1.578504e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (UK) per minute + ConversionFactor: 9,471 022 x 10⁻⁶ m³/s + Symbol: pt (UK)/min + CommonCode: L55 + Description: '' + conversion: + factor: 9.471022e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (UK) per second + ConversionFactor: 5,682 613 x 10⁻⁴ m³/s + Symbol: pt (UK)/s + CommonCode: L56 + Description: '' + conversion: + factor: 0.0005682613 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (US liquid) per day + ConversionFactor: 5,476 580 x 10⁻⁹ m³/s + Symbol: pt (US liq.)/d + CommonCode: L57 + Description: '' + conversion: + factor: 5.47658e-09 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (US liquid) per hour + ConversionFactor: 1,314 379 x 10⁻⁷ m³/s + Symbol: pt (US liq.)/h + CommonCode: L58 + Description: '' + conversion: + factor: 1.314379e-07 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (US liquid) per minute + ConversionFactor: 7,886 275 x 10⁻⁶ m³/s + Symbol: pt (US liq.)/min + CommonCode: L59 + Description: '' + conversion: + factor: 7.886275e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: pint (US liquid) per second + ConversionFactor: 4,731 765 x 10⁻⁴ m³/s + Symbol: pt (US liq.)/s + CommonCode: L60 + Description: '' + conversion: + factor: 0.0004731765 + base_units: + - MQS +- Status: X + LevelAndCategory: '2' + Name: pint (US dry) + ConversionFactor: 5,506 105 x 10⁻⁴ m³ + Symbol: pt (US dry) + CommonCode: L61 + Description: Use dry pint (common code PTD) + conversion: + factor: 0.0005506105 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '2' + Name: quart (US dry) + ConversionFactor: 1,101 221 x 10⁻³ m³ + Symbol: qt (US dry) + CommonCode: L62 + Description: Use dry quart (US) (common code QTD) + conversion: + factor: 0.001101221 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: slug per day + ConversionFactor: 1,689 109 x 10⁻⁴ kg/s + Symbol: slug/d + CommonCode: L63 + Description: '' + conversion: + factor: 0.0001689109 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: slug per foot second + ConversionFactor: 47,880 26 Pa x s + Symbol: slug/(ft·s) + CommonCode: L64 + Description: '' + conversion: + factor: 47.88026 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2' + Name: slug per cubic foot + ConversionFactor: 5,153 788 x 10² kg/m³ + Symbol: slug/ft³ + CommonCode: L65 + Description: '' + conversion: + factor: 515.3788 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: slug per hour + ConversionFactor: 4,053 861 x 10⁻³ kg/s + Symbol: slug/h + CommonCode: L66 + Description: '' + conversion: + factor: 0.004053861 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: slug per minute + ConversionFactor: 0,243 231 7 kg/s + Symbol: slug/min + CommonCode: L67 + Description: '' + conversion: + factor: 0.2432317 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: slug per second + ConversionFactor: 14,593 90 kg/s + Symbol: slug/s + CommonCode: L68 + Description: '' + conversion: + factor: 14.5939 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: tonne per kelvin + ConversionFactor: 10³ kg/K + Symbol: t/K + CommonCode: L69 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per bar + ConversionFactor: 10⁻² kg/Pa + Symbol: t/bar + CommonCode: L70 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per day + ConversionFactor: 1,157 41 x 10⁻² kg/s + Symbol: t/d + CommonCode: L71 + Description: '' + conversion: + factor: 0.0115741 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: tonne per day kelvin + ConversionFactor: 1,157 41 x 10⁻² (kg/s)/K + Symbol: "(t/d)/K" + CommonCode: L72 + Description: '' + conversion: + factor: 0.0115741 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per day bar + ConversionFactor: 1,157 41 x 10⁻⁷ (kg/s)/Pa + Symbol: "(t/d)/bar" + CommonCode: L73 + Description: '' + conversion: + factor: 1.15741e-07 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per hour kelvin + ConversionFactor: 2,777 78 x 10⁻¹ (kg/s)/K + Symbol: "(t/h)/K" + CommonCode: L74 + Description: '' + conversion: + factor: 0.277778 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per hour bar + ConversionFactor: 2,777 78 x 10⁻⁶ (kg/s)/Pa + Symbol: "(t/h)/bar" + CommonCode: L75 + Description: '' + conversion: + factor: 2.77778e-06 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per cubic metre kelvin + ConversionFactor: 10³ (kg/m³)/K + Symbol: "(t/m³)/K" + CommonCode: L76 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per cubic metre bar + ConversionFactor: 10⁻² (kg/m³)/Pa + Symbol: "(t/m³)/bar" + CommonCode: L77 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per minute + ConversionFactor: 16,666 7 kg/s + Symbol: t/min + CommonCode: L78 + Description: '' + conversion: + factor: 16.6667 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: tonne per minute kelvin + ConversionFactor: 16,666 7 (kg/s)/K + Symbol: "(t/min)/K" + CommonCode: L79 + Description: '' + conversion: + factor: 16.6667 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per minute bar + ConversionFactor: 1,666 67 x 10⁻⁴ (kg/s)/Pa + Symbol: "(t/min)/bar" + CommonCode: L80 + Description: '' + conversion: + factor: 0.000166667 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per second + ConversionFactor: 10³ kg/s + Symbol: t/s + CommonCode: L81 + Description: '' + conversion: + factor: 1000.0 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: tonne per second kelvin + ConversionFactor: 10³ (kg/s)/K + Symbol: "(t/s)/K" + CommonCode: L82 + Description: '' + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: tonne per second bar + ConversionFactor: 10⁻² (kg/s)/Pa + Symbol: "(t/s)/bar" + CommonCode: L83 + Description: '' + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: ton (UK shipping) + ConversionFactor: 1,189 3 m³ + Symbol: British shipping ton + CommonCode: L84 + Description: '' + conversion: + factor: 1.1893 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: ton long per day + ConversionFactor: 1,175 980 x 10⁻² kg/s + Symbol: ton (UK)/d + CommonCode: L85 + Description: '' + conversion: + factor: 0.0117598 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ton (US shipping) + ConversionFactor: 1,132 6 m³ + Symbol: "(US) shipping ton" + CommonCode: L86 + Description: '' + conversion: + factor: 1.1326 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: ton short per degree Fahrenheit + ConversionFactor: 1,632 932 x 10³ kg/K + Symbol: ton (US)/°F + CommonCode: L87 + Description: '' + conversion: + factor: 1632.932 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: ton short per day + ConversionFactor: 1,049 982 x 10⁻² kg/s + Symbol: ton (US)/d + CommonCode: L88 + Description: '' + conversion: + factor: 0.01049982 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: ton short per hour degree Fahrenheit + ConversionFactor: 0,453 592 2 kg/s x K + Symbol: ton (US)/(h·°F) + CommonCode: L89 + Description: '' + conversion: + factor: 0.4535922 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: ton short per hour psi + ConversionFactor: 3,654 889 x 10⁻⁵ (kg/s)/Pa + Symbol: "(ton (US)/h)/psi" + CommonCode: L90 + Description: '' + conversion: + factor: 3.654889e-05 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: ton short per psi + ConversionFactor: 0,131 576 + Symbol: ton (US)/psi + CommonCode: L91 + Description: '' + conversion: + factor: 0.131576 +- Status: '' + LevelAndCategory: '2' + Name: ton (UK long) per cubic yard + ConversionFactor: 1,328 939 x 10³ kg/m³ + Symbol: ton.l/yd³ (UK) + CommonCode: L92 + Description: '' + conversion: + factor: 1328.939 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: ton (US short) per cubic yard + ConversionFactor: 1,186 553 x 10³ kg/m³ + Symbol: ton.s/yd³ (US) + CommonCode: L93 + Description: '' + conversion: + factor: 1186.553 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: ton-force (US short) + ConversionFactor: 8,896 443 x 10³ N + Symbol: ton.sh-force + CommonCode: L94 + Description: '' + conversion: + factor: 8896.443 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: common year + ConversionFactor: 3,153 6 x 10⁷ s + Symbol: y (365 days) + CommonCode: L95 + Description: '' + conversion: + factor: 31536000.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '2' + Name: sidereal year + ConversionFactor: 3,155 815 x 10⁷ s + Symbol: y (sidereal) + CommonCode: L96 + Description: '' + conversion: + factor: 31558150.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '2' + Name: yard per degree Fahrenheit + ConversionFactor: 1,645 92 m/K + Symbol: yd/°F + CommonCode: L98 + Description: '' + conversion: + factor: 1.64592 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: yard per psi + ConversionFactor: 1,326 225 x 10⁻⁴ m/Pa + Symbol: yd/psi + CommonCode: L99 + Description: '' + conversion: + factor: 0.0001326225 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound per cubic inch + ConversionFactor: 2,767 990 x 10⁴ kg/m³ + Symbol: lb/in³ + CommonCode: LA + Description: '' + conversion: + factor: 27679.9 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '3.5' + Name: lactose excess percentage + ConversionFactor: '' + Symbol: '' + CommonCode: LAC + Description: A unit of proportion defining the percentage of lactose in a product + that exceeds a defined percentage level. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: pound + ConversionFactor: 0,453 592 37 kg + Symbol: lb + CommonCode: LBR + Description: '' + conversion: + factor: 0.45359237 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.5' + Name: troy pound (US) + ConversionFactor: 373,241 7 g + Symbol: '' + CommonCode: LBT + Description: '' + conversion: + factor: 373.2417 + base_units: [] +- Status: X + LevelAndCategory: '3.1' + Name: linear centimetre + ConversionFactor: '' + Symbol: '' + CommonCode: LC + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: litre per day + ConversionFactor: 1,157 41 x 10⁻⁸ m³/s + Symbol: l/d + CommonCode: LD + Description: '' + conversion: + factor: 1.15741e-08 + base_units: + - MQS +- Status: X + LevelAndCategory: '3.9' + Name: lite + ConversionFactor: '' + Symbol: '' + CommonCode: LE + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: leaf + ConversionFactor: '' + Symbol: '' + CommonCode: LEF + Description: A unit of count defining the number of leaves. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: linear foot + ConversionFactor: '' + Symbol: '' + CommonCode: LF + Description: A unit of count defining the number of feet (12-inch) in length of + a uniform width object. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: labour hour + ConversionFactor: '' + Symbol: '' + CommonCode: LH + Description: A unit of time defining the number of labour hours. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: linear inch + ConversionFactor: '' + Symbol: '' + CommonCode: LI + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: large spray + ConversionFactor: '' + Symbol: '' + CommonCode: LJ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: link + ConversionFactor: '' + Symbol: '' + CommonCode: LK + Description: A unit of distance equal to 0.01 chain. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: linear metre + ConversionFactor: '' + Symbol: '' + CommonCode: LM + Description: A unit of count defining the number of metres in length of a uniform + width object. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: length + ConversionFactor: '' + Symbol: '' + CommonCode: LN + Description: A unit of distance defining the linear extent of an item measured from + end to end. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: lot [unit of procurement] + ConversionFactor: '' + Symbol: '' + CommonCode: LO + Description: 'A unit of count defining the number of lots (lot: a collection of + associated items).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: liquid pound + ConversionFactor: '' + Symbol: '' + CommonCode: LP + Description: A unit of mass defining the number of pounds of a liquid substance. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: litre of pure alcohol + ConversionFactor: '' + Symbol: '' + CommonCode: LPA + Description: A unit of volume equal to one litre of pure alcohol. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: layer + ConversionFactor: '' + Symbol: '' + CommonCode: LR + Description: A unit of count defining the number of layers. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: lump sum + ConversionFactor: '' + Symbol: '' + CommonCode: LS + Description: A unit of count defining the number of whole or a complete monetary + amounts. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: ton (UK) or long ton (US) + ConversionFactor: 1,016 047 x 10³ kg + Symbol: ton (UK) + CommonCode: LTN + Description: 'Synonym: gross ton (2240 lb)' + conversion: + factor: 1016.047 + base_units: + - KGM +- Status: '' + LevelAndCategory: '1' + Name: litre + ConversionFactor: 10⁻³ m³ + Symbol: l + CommonCode: LTR + Description: '' + conversion: + factor: 0.001 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.1' + Name: metric ton, lubricating oil + ConversionFactor: '' + Symbol: '' + CommonCode: LUB + Description: A unit of mass defining the number of metric tons of lubricating oil. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: lumen + ConversionFactor: cd x sr + Symbol: lm + CommonCode: LUM + Description: '' + conversion: + factor: 1.0 + base_units: + - LUM +- Status: '' + LevelAndCategory: '1' + Name: lux + ConversionFactor: cd x sr / m² + Symbol: lx + CommonCode: LUX + Description: '' + conversion: + factor: 1.0 + base_units: + - LUX +- Status: X + LevelAndCategory: '3.1' + Name: linear yard per pound + ConversionFactor: '' + Symbol: '' + CommonCode: LX + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: linear yard + ConversionFactor: '' + Symbol: '' + CommonCode: LY + Description: A unit of count defining the number of 36-inch units in length of a + uniform width object. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.6' + Name: magnetic tape + ConversionFactor: '' + Symbol: '' + CommonCode: M0 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: milligram per litre + ConversionFactor: 10⁻³ kg/m³ + Symbol: mg/l + CommonCode: M1 + Description: '' + conversion: + factor: 0.001 + base_units: + - GL + - KMQ +- Status: '' + LevelAndCategory: '2' + Name: reciprocal cubic yard + ConversionFactor: 1,307 951 m⁻³ + Symbol: 1/yd³ + CommonCode: M10 + Description: '' + conversion: + factor: 1.307951 + base_units: + - C86 +- Status: '' + LevelAndCategory: '2' + Name: cubic yard per degree Fahrenheit + ConversionFactor: 1,376 199 m³/K + Symbol: yd³/°F + CommonCode: M11 + Description: '' + conversion: + factor: 1.376199 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: cubic yard per day + ConversionFactor: 8,849 015 x 10⁻⁶ m³/s + Symbol: yd³/d + CommonCode: M12 + Description: '' + conversion: + factor: 8.849015e-06 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: cubic yard per hour + ConversionFactor: 2,123 764 x 10⁻⁴ m³/s + Symbol: yd³/h + CommonCode: M13 + Description: '' + conversion: + factor: 0.0002123764 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: cubic yard per psi + ConversionFactor: 1,108 893 x 10⁻⁴ m³/Pa + Symbol: yd³/psi + CommonCode: M14 + Description: '' + conversion: + factor: 0.0001108893 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: cubic yard per minute + ConversionFactor: 1,274 258 x 10⁻² m³/s + Symbol: yd³/min + CommonCode: M15 + Description: '' + conversion: + factor: 0.01274258 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: cubic yard per second + ConversionFactor: 0,764 554 9 m³/s + Symbol: yd³/s + CommonCode: M16 + Description: '' + conversion: + factor: 0.7645549 + base_units: + - MQS +- Status: '' + LevelAndCategory: '2' + Name: kilohertz metre + ConversionFactor: 10³ Hz x m + Symbol: kHz·m + CommonCode: M17 + Description: '' + conversion: + factor: 1000.0 + base_units: + - H34 +- Status: '' + LevelAndCategory: '2' + Name: gigahertz metre + ConversionFactor: 10⁹ Hz x m + Symbol: GHz·m + CommonCode: M18 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - H34 +- Status: '' + LevelAndCategory: '3' + Name: Beaufort + ConversionFactor: '' + Symbol: Bft + CommonCode: M19 + Description: An empirical measure for describing wind speed based mainly on observed + sea conditions. The Beaufort scale indicates the wind speed by numbers that typically + range from 0 for calm, to 12 for hurricane. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: reciprocal megakelvin or megakelvin to the power minus one + ConversionFactor: 10⁻⁶ K⁻¹ + Symbol: 1/MK + CommonCode: M20 + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - C91 + - N83 +- Status: '' + LevelAndCategory: '2' + Name: reciprocal kilovolt - ampere reciprocal hour + ConversionFactor: 2,777 778 x 10⁻⁷ (V x A x s)⁻¹ + Symbol: 1/kVAh + CommonCode: M21 + Description: '' + conversion: + factor: 2.777778e-07 + base_units: + - M30 +- Status: '' + LevelAndCategory: '2' + Name: millilitre per square centimetre minute + ConversionFactor: 2,777 778 x 10⁻⁶ (m³/s)/m² + Symbol: "(ml/min)/cm²" + CommonCode: M22 + Description: '' + conversion: + factor: 2.777778e-06 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: newton per centimetre + ConversionFactor: 10² N/m + Symbol: N/cm + CommonCode: M23 + Description: '' + conversion: + factor: 100.0 + base_units: + - 4P +- Status: '' + LevelAndCategory: 1M + Name: ohm kilometre + ConversionFactor: 10³ Ω x m + Symbol: Ω·km + CommonCode: M24 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C61 +- Status: '' + LevelAndCategory: '3.7' + Name: percent per degree Celsius + ConversionFactor: 10⁻² °C⁻¹ + Symbol: "%/°C" + CommonCode: M25 + Description: A unit of proportion, equal to 0.01, in relation to a temperature of + one degree. + conversion: + factor: 0.01 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: gigaohm per metre + ConversionFactor: 10⁹ Ω/m + Symbol: GΩ/m + CommonCode: M26 + Description: '' + conversion: + factor: 1000000000.0 + base_units: + - H26 +- Status: '' + LevelAndCategory: '2' + Name: megahertz metre + ConversionFactor: 10⁶ Hz x m + Symbol: MHz·m + CommonCode: M27 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - H34 +- Status: '' + LevelAndCategory: 1S + Name: kilogram per kilogram + ConversionFactor: '1' + Symbol: kg/kg + CommonCode: M29 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: reciprocal volt - ampere reciprocal second + ConversionFactor: "(V x A x s)⁻¹" + Symbol: 1/(V·A·s) + CommonCode: M30 + Description: '' + conversion: + factor: 1.0 + base_units: + - M30 +- Status: '' + LevelAndCategory: 1S + Name: kilogram per kilometre + ConversionFactor: 10⁻³ kg/m + Symbol: kg/km + CommonCode: M31 + Description: '' + conversion: + factor: 0.001 + base_units: + - KL +- Status: '' + LevelAndCategory: '2' + Name: pascal second per litre + ConversionFactor: 10³ Pa x s/m³ + Symbol: Pa·s/l + CommonCode: M32 + Description: '' + conversion: + factor: 1000.0 + base_units: + - C66 +- Status: '' + LevelAndCategory: 1S + Name: millimole per litre + ConversionFactor: mol/m³ + Symbol: mmol/l + CommonCode: M33 + Description: '' + conversion: + factor: 1.0 + base_units: + - C36 + - M33 +- Status: '' + LevelAndCategory: 1S + Name: newton metre per square metre + ConversionFactor: N x m/m² + Symbol: N·m/m² + CommonCode: M34 + Description: '' + conversion: + factor: 1.0 + base_units: + - M34 +- Status: '' + LevelAndCategory: 1S + Name: millivolt - ampere + ConversionFactor: 10⁻³ V x A + Symbol: mV·A + CommonCode: M35 + Description: '' + conversion: + factor: 0.001 + base_units: + - D44 +- Status: '' + LevelAndCategory: '3.7' + Name: 30-day month + ConversionFactor: 2,592 000 x 10⁶ s + Symbol: mo (30 days) + CommonCode: M36 + Description: A unit of count defining the number of months expressed in multiples + of 30 days, one day equals 24 hours. + conversion: + factor: 2592000.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.7' + Name: actual/360 + ConversionFactor: 3,110 400 0 x 10⁷ s + Symbol: y (360 days) + CommonCode: M37 + Description: A unit of count defining the number of years expressed in multiples + of 360 days, one day equals 24 hours. + conversion: + factor: 31104000.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1M + Name: kilometre per second squared + ConversionFactor: 10³ m/s² + Symbol: km/s² + CommonCode: M38 + Description: 1000-fold of the SI base unit metre divided by the power of the SI + base unit second by exponent 2. + conversion: + factor: 1000.0 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: 1M + Name: centimetre per second squared + ConversionFactor: 10⁻² m/s² + Symbol: cm/s² + CommonCode: M39 + Description: 0,01-fold of the SI base unit metre divided by the power of the SI + base unit second by exponent 2. + conversion: + factor: 0.01 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: '3.9' + Name: monetary value + ConversionFactor: '' + Symbol: '' + CommonCode: M4 + Description: A unit of measure expressed as a monetary amount. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: yard per second squared + ConversionFactor: 9,144 x 10⁻¹ m/s² + Symbol: yd/s² + CommonCode: M40 + Description: Unit of the length according to the Anglo-American and Imperial system + of units divided by the power of the SI base unit second by exponent 2. + conversion: + factor: 0.9144 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: 1M + Name: millimetre per second squared + ConversionFactor: 10⁻³ m/s² + Symbol: mm/s² + CommonCode: M41 + Description: 0,001-fold of the SI base unit metre divided by the power of the SI + base unit second by exponent 2. + conversion: + factor: 0.001 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: '2' + Name: mile (statute mile) per second squared + ConversionFactor: 1,609 344 x 10³ m/s² + Symbol: mi/s² + CommonCode: M42 + Description: Unit of the length according to the Imperial system of units divided + by the power of the SI base unit second by exponent 2. + conversion: + factor: 1609.344 + base_units: + - MSK + - P79 +- Status: '' + LevelAndCategory: '2' + Name: mil + ConversionFactor: 9,817 477 x 10⁻⁴ rad + Symbol: mil + CommonCode: M43 + Description: Unit to indicate an angle at military zone, equal to the 6400th part + of the full circle of the 360° or 2·p·rad. + conversion: + factor: 0.0009817477 + base_units: + - C81 +- Status: '' + LevelAndCategory: '2' + Name: revolution + ConversionFactor: 6,283 185 rad + Symbol: rev + CommonCode: M44 + Description: Unit to identify an angle of the full circle of 360° or 2·p·rad (Refer + ISO/TC12 SI Guide). + conversion: + factor: 6.283185 + base_units: + - C81 +- Status: '' + LevelAndCategory: 1M + Name: degree [unit of angle] per second squared + ConversionFactor: 1,745 329 x 10⁻² rad / s + Symbol: "°/s²" + CommonCode: M45 + Description: 360 part of a full circle divided by the power of the SI base unit + second and the exponent 2. + conversion: + factor: 0.01745329 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: revolution per minute + ConversionFactor: 0,104 719 8 rad/s + Symbol: r/min + CommonCode: M46 + Description: Unit of the angular velocity. + conversion: + factor: 0.1047198 + base_units: + - 2A +- Status: '' + LevelAndCategory: '2' + Name: circular mil + ConversionFactor: 5,067 075 x 10⁻¹⁰ m² + Symbol: cmil + CommonCode: M47 + Description: 'Unit of an area, of which the size is given by a diameter of length + of 1 mm (0,001 in) based on the formula: area = p·(diameter/2)².' + conversion: + factor: 5.067075e-10 + base_units: + - MTK +- Status: '' + LevelAndCategory: '2' + Name: square mile (based on U.S. survey foot) + ConversionFactor: 2,589 998 x 10⁶ m² + Symbol: mi² (US survey) + CommonCode: M48 + Description: Unit of the area, which is mainly common in the agriculture and forestry. + conversion: + factor: 2589998.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: '2' + Name: chain (based on U.S. survey foot) + ConversionFactor: 2,011684 x 10 m + Symbol: 'ch (US survey) ' + CommonCode: M49 + Description: Unit of the length according the Anglo-American system of units. + conversion: + factor: 2.011684 + base_units: + - MTR +- Status: '' + LevelAndCategory: 2S + Name: microcurie + ConversionFactor: 3,7 x 10⁴ Bq + Symbol: µCi + CommonCode: M5 + Description: '' + conversion: + factor: 37000.0 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: furlong + ConversionFactor: 2,011 68 x 10² m + Symbol: fur + CommonCode: M50 + Description: 'Unit commonly used in Great Britain at rural distances: 1 furlong + = 40 rods = 10 chains (UK) = 1/8 mile = 1/10 furlong = 220 yards = 660 foot.' + conversion: + factor: 201.168 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: foot (U.S. survey) + ConversionFactor: 3,048 006 x 10⁻¹ m + Symbol: 'ft (US survey) ' + CommonCode: M51 + Description: Unit commonly used in the United States for ordnance survey., + conversion: + factor: 0.3048006 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: mile (based on U.S. survey foot) + ConversionFactor: 1,609347 x 10³ m + Symbol: 'mi (US survey) ' + CommonCode: M52 + Description: Unit commonly used in the United States for ordnance survey. + conversion: + factor: 1609.347 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1M + Name: metre per pascal + ConversionFactor: kg⁻¹ x m² x s² + Symbol: m/Pa + CommonCode: M53 + Description: SI base unit metre divided by the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - M53 +- Status: '' + LevelAndCategory: 1S + Name: metre per radiant + ConversionFactor: m/rad + Symbol: m/rad + CommonCode: M55 + Description: Unit of the translation factor for implementation from rotation to + linear movement. + conversion: + factor: 1.0 + base_units: + - M55 +- Status: '' + LevelAndCategory: '2' + Name: shake + ConversionFactor: 10⁻⁸ s + Symbol: shake + CommonCode: M56 + Description: Unit for a very short period. + conversion: + factor: 1.0e-08 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '2' + Name: mile per minute + ConversionFactor: 26,822 4 m/s + Symbol: mi/min + CommonCode: M57 + Description: Unit of velocity from the Imperial system of units. + conversion: + factor: 26.8224 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: mile per second + ConversionFactor: 1,609 344 x 10³ m/s + Symbol: mi/s + CommonCode: M58 + Description: Unit of the velocity from the Imperial system of units. + conversion: + factor: 1609.344 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: 1S + Name: metre per second pascal + ConversionFactor: m² x kg⁻¹ x s + Symbol: "(m/s)/Pa" + CommonCode: M59 + Description: SI base unit meter divided by the product of SI base unit second and + the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - M59 +- Status: '' + LevelAndCategory: '2' + Name: metre per hour + ConversionFactor: 2,777 78 x 10⁻⁴ m/s + Symbol: m/h + CommonCode: M60 + Description: SI base unit metre divided by the unit hour. + conversion: + factor: 0.000277778 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: inch per year + ConversionFactor: 8,048 774 x 10⁻¹⁰ m/s + Symbol: in/y + CommonCode: M61 + Description: Unit of the length according to the Anglo-American and Imperial system + of units divided by the unit common year with 365 days. + conversion: + factor: 8.048774e-10 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: kilometre per second + ConversionFactor: 10³ m/s + Symbol: km/s + CommonCode: M62 + Description: 1000-fold of the SI base unit metre divided by the SI base unit second. + conversion: + factor: 1000.0 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: inch per minute + ConversionFactor: 4,233 333 x 10⁻⁴ m/s + Symbol: in/min + CommonCode: M63 + Description: Unit inch according to the Anglo-American and Imperial system of units + divided by the unit minute. + conversion: + factor: 0.0004233333 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: yard per second + ConversionFactor: 9,144 x 10⁻¹ m/s + Symbol: yd/s + CommonCode: M64 + Description: Unit yard according to the Anglo-American and Imperial system of units + divided by the SI base unit second. + conversion: + factor: 0.9144 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: yard per minute + ConversionFactor: 1,524 x 10⁻² m/s + Symbol: yd/min + CommonCode: M65 + Description: Unit yard according to the Anglo-American and Imperial system of units + divided by the unit minute. + conversion: + factor: 0.01524 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: yard per hour + ConversionFactor: 2,54 x 10⁻⁴ m/s + Symbol: yd/h + CommonCode: M66 + Description: Unit yard according to the Anglo-American and Imperial system of units + divided by the unit hour. + conversion: + factor: 0.000254 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '2' + Name: acre-foot (based on U.S. survey foot) + ConversionFactor: 1,233 489 x 10³ m³ + Symbol: acre-ft (US survey) + CommonCode: M67 + Description: Unit of the volume, which is used in the United States to measure/gauge + the capacity of reservoirs. + conversion: + factor: 1233.489 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: cord (128 ft3) + ConversionFactor: 3,624 556 m³ + Symbol: cord + CommonCode: M68 + Description: Traditional unit of the volume of stacked firewood which has been measured + with a cord. + conversion: + factor: 3.624556 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: cubic mile (UK statute) + ConversionFactor: 4,168 182 x 10⁹ m³ + Symbol: mi³ + CommonCode: M69 + Description: Unit of volume according to the Imperial system of units. + conversion: + factor: 4168182000.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: micro-inch + ConversionFactor: 25,4 x 10⁻⁹ m + Symbol: µin + CommonCode: M7 + Description: '' + conversion: + factor: 2.54e-08 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: ton, register + ConversionFactor: 2,831 685 m³ + Symbol: RT + CommonCode: M70 + Description: Traditional unit of the cargo capacity. + conversion: + factor: 2.831685 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: cubic metre per pascal + ConversionFactor: kg⁻¹ x m⁴ x s² + Symbol: m³/Pa + CommonCode: M71 + Description: Power of the SI base unit meter by exponent 3 divided by the derived + SI base unit pascal. + conversion: + factor: 1.0 + base_units: + - M71 +- Status: '' + LevelAndCategory: 1M + Name: bel + ConversionFactor: B + Symbol: B + CommonCode: M72 + Description: Logarithmic relationship to base 10. + conversion: + factor: 1.0 + base_units: + - M72 +- Status: '' + LevelAndCategory: 1M + Name: kilogram per cubic metre pascal + ConversionFactor: m⁻² x s² + Symbol: "(kg/m³)/Pa" + CommonCode: M73 + Description: SI base unit kilogram divided by the product of the power of the SI + base unit metre with exponent 3 and the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - M73 +- Status: '' + LevelAndCategory: '2.0' + Name: kilogram per pascal + ConversionFactor: m x s² + Symbol: kg/Pa + CommonCode: M74 + Description: SI base unit kilogram divided by the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - M74 +- Status: '' + LevelAndCategory: '2' + Name: kilopound-force + ConversionFactor: 4,448 222 x 10³ N + Symbol: kip + CommonCode: M75 + Description: 1000-fold of the unit of the force pound-force (lbf) according to the + Anglo-American system of units with the relationship. + conversion: + factor: 4448.222 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: poundal + ConversionFactor: 1,382 550 x 10⁻¹ N + Symbol: pdl + CommonCode: M76 + Description: Non SI-conforming unit of the power, which corresponds to a mass of + a pound multiplied with the acceleration of a foot per square second. + conversion: + factor: 0.138255 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: kilogram metre per second squared + ConversionFactor: "(kg x m)/s²" + Symbol: kg·m/s² + CommonCode: M77 + Description: Product of the SI base unit kilogram and the SI base unit metre divided + by the power of the SI base unit second by exponent 2. + conversion: + factor: 1.0 + base_units: + - M77 + - NEW +- Status: '' + LevelAndCategory: '2' + Name: pond + ConversionFactor: 9,806 65 x 10⁻³ N + Symbol: p + CommonCode: M78 + Description: 0,001-fold of the unit of the weight, defined as a mass of 1 kg which + finds out about a weight strength from 1 kp by the gravitational force at sea + level which corresponds to a strength of 9,806 65 newton. + conversion: + factor: 0.00980665 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: square foot per hour + ConversionFactor: 2,580 64 x 10⁻⁵ m²/s + Symbol: ft²/h + CommonCode: M79 + Description: Power of the unit foot according to the Anglo-American and Imperial + system of units by exponent 2 divided by the unit of time hour. + conversion: + factor: 2.58064e-05 + base_units: + - S4 +- Status: '' + LevelAndCategory: '2' + Name: stokes per pascal + ConversionFactor: 10⁻⁴ kg⁻¹ x m³ x s + Symbol: St/Pa + CommonCode: M80 + Description: CGS (Centimetre-Gram-Second system) unit stokes divided by the derived + SI unit pascal. + conversion: + factor: 0.0001 + base_units: + - M82 +- Status: '' + LevelAndCategory: '2' + Name: square centimetre per second + ConversionFactor: 10⁻⁴ m²/s + Symbol: cm²/s + CommonCode: M81 + Description: 0,000 1-fold of the power of the SI base unit metre by exponent 2 divided + by the SI base unit second. + conversion: + factor: 0.0001 + base_units: + - S4 +- Status: '' + LevelAndCategory: '1.0' + Name: square metre per second pascal + ConversionFactor: kg⁻¹ x m³ x s + Symbol: "(m²/s)/Pa" + CommonCode: M82 + Description: Power of the SI base unit metre with the exponent 2 divided by the + SI base unit second and the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - M82 +- Status: '' + LevelAndCategory: '2.0' + Name: denier + ConversionFactor: 1,111 111 x 10⁻⁷ kg/m + Symbol: den + CommonCode: M83 + Description: Traditional unit for the indication of the linear mass of textile fibers + and yarns. + conversion: + factor: 1.111111e-07 + base_units: + - KL +- Status: '' + LevelAndCategory: '2' + Name: pound per yard + ConversionFactor: 4,960 546 x 10⁻¹ kg/m + Symbol: lb/yd + CommonCode: M84 + Description: Unit for linear mass according to avoirdupois system of units. + conversion: + factor: 0.4960546 + base_units: + - KL +- Status: '' + LevelAndCategory: '2' + Name: ton, assay + ConversionFactor: 2,916 667 x 10⁻² kg + Symbol: '' + CommonCode: M85 + Description: Non SI-conforming unit of the mass used in the mineralogy to determine + the concentration of precious metals in ore according to the mass of the precious + metal in milligrams in a sample of the mass of an assay sound (number of troy + ounces in a short ton (1 000 lb)). + conversion: + factor: 0.02916667 + base_units: + - KGM +- Status: '' + LevelAndCategory: '2' + Name: pfund + ConversionFactor: 0,5 kg + Symbol: pfd + CommonCode: M86 + Description: Outdated unit of the mass used in Germany. + conversion: + factor: 0.5 + base_units: + - KGM +- Status: '' + LevelAndCategory: 1S + Name: kilogram per second pascal + ConversionFactor: m x s + Symbol: "(kg/s)/Pa" + CommonCode: M87 + Description: SI base unit kilogram divided by the product of the SI base unit second + and the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - M87 +- Status: '' + LevelAndCategory: '2' + Name: tonne per month + ConversionFactor: 3,802 570 537 68 x 10⁻⁴ kg/s + Symbol: t/mo + CommonCode: M88 + Description: Unit tonne divided by the unit month. + conversion: + factor: 0.000380257053768 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2' + Name: tonne per year + ConversionFactor: 3,168 808 781 x 10⁻⁵ kg/s + Symbol: t/y + CommonCode: M89 + Description: Unit tonne divided by the unit year with 365 days. + conversion: + factor: 3.168808781e-05 + base_units: + - KGS +- Status: '' + LevelAndCategory: '3.9' + Name: million Btu per 1000 cubic foot + ConversionFactor: '' + Symbol: MBTU/kft³ + CommonCode: M9 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: kilopound per hour + ConversionFactor: 0,125 997 889 kg/s + Symbol: klb/h + CommonCode: M90 + Description: 1000-fold of the unit of the mass avoirdupois pound according to the + avoirdupois unit system divided by the unit hour. + conversion: + factor: 0.125997889 + base_units: + - KGS +- Status: '' + LevelAndCategory: '2.0' + Name: pound per pound + ConversionFactor: '1.0' + Symbol: lb/lb + CommonCode: M91 + Description: Proportion of the mass consisting of the avoirdupois pound according + to the avoirdupois unit system divided by the avoirdupois pound according to the + avoirdupois unit system. + conversion: + factor: 1.0 + base_units: + - E98 + - F02 + - H60 + - L52 + - M91 +- Status: '' + LevelAndCategory: '2' + Name: pound-force foot + ConversionFactor: 1,355 818 N x m + Symbol: lbf·ft + CommonCode: M92 + Description: Product of the unit pound-force according to the Anglo-American system + of units and the unit foot according to the Anglo-American and the Imperial system + of units. + conversion: + factor: 1.355818 + base_units: + - NU +- Status: '' + LevelAndCategory: 1M + Name: newton metre per radian + ConversionFactor: m² x kg x s⁻² x rad⁻¹ + Symbol: N·m/rad + CommonCode: M93 + Description: Product of the derived SI unit newton and the SI base unit metre divided + by the unit radian. + conversion: + factor: 1.0 + base_units: + - M93 +- Status: '' + LevelAndCategory: 1S + Name: kilogram metre + ConversionFactor: kg x m + Symbol: kg·m + CommonCode: M94 + Description: Unit of imbalance as a product of the SI base unit kilogram and the + SI base unit metre. + conversion: + factor: 1.0 + base_units: + - M94 +- Status: '' + LevelAndCategory: '2' + Name: poundal foot + ConversionFactor: 4,214 011 x 10⁻² N x m + Symbol: pdl·ft + CommonCode: M95 + Description: Product of the non SI-conforming unit of the force poundal and the + unit foot according to the Anglo-American and Imperial system of units . + conversion: + factor: 0.04214011 + base_units: + - NU +- Status: '' + LevelAndCategory: '2' + Name: poundal inch + ConversionFactor: 3,511 677 10⁻³ N x m + Symbol: pdl·in + CommonCode: M96 + Description: Product of the non SI-conforming unit of the force poundal and the + unit inch according to the Anglo-American and Imperial system of units . + conversion: + factor: 1.0 + base_units: + - NU +- Status: '' + LevelAndCategory: '2' + Name: dyne metre + ConversionFactor: 10⁻⁵ N x m + Symbol: dyn·m + CommonCode: M97 + Description: CGS (Centimetre-Gram-Second system) unit of the rotational moment. + conversion: + factor: 1.0e-05 + base_units: + - NU +- Status: '' + LevelAndCategory: 1M + Name: kilogram centimetre per second + ConversionFactor: 10⁻² kg x m/s + Symbol: kg·(cm/s) + CommonCode: M98 + Description: Product of the SI base unit kilogram and the 0,01-fold of the SI base + unit metre divided by the SI base unit second. + conversion: + factor: 0.01 + base_units: + - B31 +- Status: '' + LevelAndCategory: 1M + Name: gram centimetre per second + ConversionFactor: 10⁻⁵ kg x m/s + Symbol: g·(cm/s) + CommonCode: M99 + Description: Product of the 0,001-fold of the SI base unit kilogram and the 0,01-fold + of the SI base unit metre divided by the SI base unit second. + conversion: + factor: 1.0e-05 + base_units: + - B31 +- Status: X + LevelAndCategory: '3.9' + Name: machine per unit + ConversionFactor: '' + Symbol: '' + CommonCode: MA + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: megavolt ampere reactive hour + ConversionFactor: '' + Symbol: Mvar·h + CommonCode: MAH + Description: A unit of electrical reactive power defining the total amount of reactive + power across a power system. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: megalitre + ConversionFactor: 10³ m³ + Symbol: Ml + CommonCode: MAL + Description: '' + conversion: + factor: 1000.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: megametre + ConversionFactor: 10⁶ m + Symbol: Mm + CommonCode: MAM + Description: '' + conversion: + factor: 1000000.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1M + Name: megavar + ConversionFactor: '' + Symbol: Mvar + CommonCode: MAR + Description: A unit of electrical reactive power represented by a current of one + thousand amperes flowing due a potential difference of one thousand volts where + the sine of the phase angle between them is 1. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: megawatt + ConversionFactor: 10⁶ W + Symbol: MW + CommonCode: MAW + Description: A unit of power defining the rate of energy transferred or consumed + when a current of 1000 amperes flows due to a potential of 1000 volts at unity + power factor. + conversion: + factor: 1000000.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '3.5' + Name: thousand standard brick equivalent + ConversionFactor: '' + Symbol: '' + CommonCode: MBE + Description: A unit of count defining the number of one thousand brick equivalent + units. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: thousand board foot + ConversionFactor: '' + Symbol: '' + CommonCode: MBF + Description: A unit of volume equal to one thousand board foot. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: millibar + ConversionFactor: 10² Pa + Symbol: mbar + CommonCode: MBR + Description: '' + conversion: + factor: 100.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: 1S + Name: microgram + ConversionFactor: 10⁻⁹ kg + Symbol: µg + CommonCode: MC + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - KGM +- Status: '' + LevelAndCategory: 2S + Name: millicurie + ConversionFactor: 3,7 x 10⁷ Bq + Symbol: mCi + CommonCode: MCU + Description: '' + conversion: + factor: 37000000.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.1' + Name: air dry metric ton + ConversionFactor: '' + Symbol: '' + CommonCode: MD + Description: A unit of count defining the number of metric tons of a product, disregarding + the water content of the product. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: milligram per square foot per side + ConversionFactor: '' + Symbol: '' + CommonCode: MF + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: milligram + ConversionFactor: 10⁻⁶ kg + Symbol: mg + CommonCode: MGM + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - KGM +- Status: '' + LevelAndCategory: 1S + Name: megahertz + ConversionFactor: 10⁶ Hz + Symbol: MHz + CommonCode: MHZ + Description: '' + conversion: + factor: 1000000.0 + base_units: + - HTZ +- Status: '' + LevelAndCategory: '2' + Name: square mile (statute mile) + ConversionFactor: 2,589 988 km² + Symbol: mi² + CommonCode: MIK + Description: '' + conversion: + factor: 2.589988 + base_units: [] +- Status: '' + LevelAndCategory: '3.7' + Name: thousand + ConversionFactor: 10³ + Symbol: '' + CommonCode: MIL + Description: '' + conversion: + factor: 1000.0 +- Status: '' + LevelAndCategory: '1' + Name: minute [unit of time] + ConversionFactor: 60 s + Symbol: min + CommonCode: MIN + Description: '' + conversion: + factor: 60.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.7' + Name: million + ConversionFactor: 10⁶ + Symbol: '' + CommonCode: MIO + Description: '' + conversion: + factor: 1000000.0 +- Status: '' + LevelAndCategory: '3.7' + Name: million international unit + ConversionFactor: '' + Symbol: '' + CommonCode: MIU + Description: A unit of count defining the number of international units in multiples + of 10⁶. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: milligram per square inch + ConversionFactor: '' + Symbol: mg/in² + CommonCode: MK + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: milliard + ConversionFactor: 10⁹ + Symbol: '' + CommonCode: MLD + Description: 'Synonym: billion (US)' + conversion: + factor: 1000000000.0 +- Status: '' + LevelAndCategory: 1S + Name: millilitre + ConversionFactor: 10⁻⁶ m³ + Symbol: ml + CommonCode: MLT + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: square millimetre + ConversionFactor: 10⁻⁶ m² + Symbol: mm² + CommonCode: MMK + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - MTK +- Status: '' + LevelAndCategory: 1S + Name: cubic millimetre + ConversionFactor: 10⁻⁹ m³ + Symbol: mm³ + CommonCode: MMQ + Description: '' + conversion: + factor: 1.0e-09 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: millimetre + ConversionFactor: 10⁻³ m + Symbol: mm + CommonCode: MMT + Description: '' + conversion: + factor: 0.001 + base_units: + - MTR +- Status: '' + LevelAndCategory: '3.1' + Name: kilogram, dry weight + ConversionFactor: '' + Symbol: '' + CommonCode: MND + Description: A unit of mass defining the number of kilograms of a product, disregarding + the water content of the product. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: month + ConversionFactor: 2,629 800 x 10⁶ s + Symbol: mo + CommonCode: MON + Description: Unit of time equal to 1/12 of a year of 365,25 days. + conversion: + factor: 2629800.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1S + Name: megapascal + ConversionFactor: 10⁶ Pa + Symbol: MPa + CommonCode: MPA + Description: '' + conversion: + factor: 1000000.0 + base_units: + - C55 + - PAL +- Status: X + LevelAndCategory: '3.8' + Name: thousand metre + ConversionFactor: 10³m + Symbol: '' + CommonCode: MQ + Description: '' + conversion: + factor: 1000.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per hour + ConversionFactor: 2,777 78 x 10⁻⁴ m³/s + Symbol: m³/h + CommonCode: MQH + Description: '' + conversion: + factor: 0.000277778 + base_units: + - MQS +- Status: '' + LevelAndCategory: '1' + Name: cubic metre per second + ConversionFactor: m³/s + Symbol: m³/s + CommonCode: MQS + Description: '' + conversion: + factor: 1.0 + base_units: + - MQS +- Status: '' + LevelAndCategory: '1' + Name: metre per second squared + ConversionFactor: m/s² + Symbol: m/s² + CommonCode: MSK + Description: '' + conversion: + factor: 1.0 + base_units: + - MSK + - P79 +- Status: X + LevelAndCategory: '3.3' + Name: mat + ConversionFactor: '' + Symbol: '' + CommonCode: MT + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: square metre + ConversionFactor: m² + Symbol: m² + CommonCode: MTK + Description: '' + conversion: + factor: 1.0 + base_units: + - MTK +- Status: '' + LevelAndCategory: '1' + Name: cubic metre + ConversionFactor: m³ + Symbol: m³ + CommonCode: MTQ + Description: 'Synonym: metre cubed' + conversion: + factor: 1.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '1' + Name: metre + ConversionFactor: m + Symbol: m + CommonCode: MTR + Description: '' + conversion: + factor: 1.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: '1' + Name: metre per second + ConversionFactor: m/s + Symbol: m/s + CommonCode: MTS + Description: '' + conversion: + factor: 1.0 + base_units: + - MTS + - P87 +- Status: X + LevelAndCategory: '3.7' + Name: number of mults + ConversionFactor: '' + Symbol: '' + CommonCode: MV + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: megavolt - ampere + ConversionFactor: 10⁶ V x A + Symbol: MV·A + CommonCode: MVA + Description: '' + conversion: + factor: 1000000.0 + base_units: + - D44 +- Status: '' + LevelAndCategory: 1S + Name: megawatt hour (1000 kW.h) + ConversionFactor: 3,6 x 10⁹ J + Symbol: MW·h + CommonCode: MWH + Description: A unit of power defining the total amount of bulk energy transferred + or consumed. + conversion: + factor: 3600000000.0 + base_units: + - JOU +- Status: '' + LevelAndCategory: '3.9' + Name: pen calorie + ConversionFactor: '' + Symbol: '' + CommonCode: N1 + Description: A unit of count defining the number of calories prescribed daily for + parenteral/enteral therapy. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2.0' + Name: pound foot per second + ConversionFactor: 1,382 550 x 10⁻¹ kg x m/s + Symbol: lb·(ft/s) + CommonCode: N10 + Description: Product of the avoirdupois pound according to the avoirdupois unit + system and the unit foot according to the Anglo-American and Imperial system of + units divided by the SI base unit second. + conversion: + factor: 0.138255 + base_units: + - B31 +- Status: '' + LevelAndCategory: '2.0' + Name: pound inch per second + ConversionFactor: 1,152 125 x 10⁻² kg x m/s + Symbol: lb·(in/s) + CommonCode: N11 + Description: Product of the avoirdupois pound according to the avoirdupois unit + system and the unit inch according to the Anglo-American and Imperial system of + units divided by the SI base unit second. + conversion: + factor: 0.01152125 + base_units: + - B31 +- Status: '' + LevelAndCategory: '2' + Name: Pferdestaerke + ConversionFactor: 7,354 988 x 10² W + Symbol: PS + CommonCode: N12 + Description: 'Obsolete unit of the power relating to DIN 1301-3:1979: 1 PS = 735,498 + 75 W.' + conversion: + factor: 735.4988 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: '2' + Name: centimetre of mercury (0 ºC) + ConversionFactor: 1,333 22 x 10³ Pa + Symbol: cmHg (0 ºC) + CommonCode: N13 + Description: Non SI-conforming unit of pressure, at which a value of 1 cmHg meets + the static pressure, which is generated by a mercury at a temperature of 0 °C + with a height of 1 centimetre . + conversion: + factor: 1333.22 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: centimetre of water (4 ºC) + ConversionFactor: 9,806 38 x 10 Pa + Symbol: cmH₂O (4 °C) + CommonCode: N14 + Description: Non SI-conforming unit of pressure, at which a value of 1 cmH2O meets + the static pressure, which is generated by a head of water at a temperature of + 4 °C with a height of 1 centimetre . + conversion: + factor: 9.80638 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: foot of water (39.2 ºF) + ConversionFactor: 2,988 98 x 10³ Pa + Symbol: ftH₂O (39,2 ºF) + CommonCode: N15 + Description: Non SI-conforming unit of pressure according to the Anglo-American + and Imperial system for units, whereas the value of 1 ftH2O is equivalent to the + static pressure, which is generated by a head of water at a temperature 39,2°F + with a height of 1 foot . + conversion: + factor: 2988.98 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: inch of mercury (32 ºF) + ConversionFactor: 3,386 38 x 10³ Pa + Symbol: inHG (32 ºF) + CommonCode: N16 + Description: Non SI-conforming unit of pressure according to the Anglo-American + and Imperial system for units, whereas the value of 1 inHg meets the static pressure, + which is generated by a mercury at a temperature of 32°F with a height of 1 inch. + conversion: + factor: 3386.38 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: inch of mercury (60 ºF) + ConversionFactor: 3,376 85 x 10³ Pa + Symbol: inHg (60 ºF) + CommonCode: N17 + Description: Non SI-conforming unit of pressure according to the Anglo-American + and Imperial system for units, whereas the value of 1 inHg meets the static pressure, + which is generated by a mercury at a temperature of 60°F with a height of 1 inch. + conversion: + factor: 3376.85 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: inch of water (39.2 ºF) + ConversionFactor: 2,490 82 × 10² Pa + Symbol: inH₂O (39,2 ºF) + CommonCode: N18 + Description: Non SI-conforming unit of pressure according to the Anglo-American + and Imperial system for units, whereas the value of 1 inH2O meets the static pressure, + which is generated by a head of water at a temperature of 39,2°F with a height + of 1 inch . + conversion: + factor: 2.49082 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: inch of water (60 ºF) + ConversionFactor: 2,488 4 × 10² Pa + Symbol: inH₂O (60 ºF) + CommonCode: N19 + Description: Non SI-conforming unit of pressure according to the Anglo-American + and Imperial system for units, whereas the value of 1 inH2O meets the static pressure, + which is generated by a head of water at a temperature of 60°F with a height of + 1 inch . + conversion: + factor: 2.4884 + base_units: [] +- Status: X + LevelAndCategory: '3.9' + Name: number of lines + ConversionFactor: '' + Symbol: '' + CommonCode: N2 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: kip per square inch + ConversionFactor: 6,894 757 x 10⁶ Pa + Symbol: ksi + CommonCode: N20 + Description: Non SI-conforming unit of the pressure according to the Anglo-American + system of units as the 1000-fold of the unit of the force pound-force divided + by the power of the unit inch by exponent 2. + conversion: + factor: 6894757.0 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: poundal per square foot + ConversionFactor: 1,488 164 Pa + Symbol: pdl/ft² + CommonCode: N21 + Description: 'Non SI-conforming unit of pressure by the Imperial system of units + according to NIST: 1 pdl/ft² = 1,488 164 Pa.' + conversion: + factor: 1.488164 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) per square inch + ConversionFactor: 4,394 185 x 10 kg/m² + Symbol: oz/in² + CommonCode: N22 + Description: Unit of the surface specific mass (avoirdupois ounce according to the + avoirdupois system of units according to the surface square inch according to + the Anglo-American and Imperial system of units). + conversion: + factor: 4.394185 + base_units: + - '28' +- Status: '' + LevelAndCategory: '2' + Name: conventional metre of water + ConversionFactor: 9,806 65 x 10³ Pa + Symbol: mH₂O + CommonCode: N23 + Description: Not SI-conforming unit of pressure, whereas a value of 1 mH2O is equivalent + to the static pressure, which is produced by one metre high water column . + conversion: + factor: 9806.65 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '2' + Name: gram per square millimetre + ConversionFactor: 10³ kg/m² + Symbol: g/mm² + CommonCode: N24 + Description: 0,001-fold of the SI base unit kilogram divided by the 0.000 001-fold + of the power of the SI base unit meter by exponent 2. + conversion: + factor: 1000.0 + base_units: + - '28' +- Status: '' + LevelAndCategory: '2' + Name: pound per square yard + ConversionFactor: 5,424 919 x 10⁻¹ kg/m² + Symbol: lb/yd² + CommonCode: N25 + Description: Unit for areal-related mass as a unit pound according to the avoirdupois + unit system divided by the power of the unit yard according to the Anglo-American + and Imperial system of units with exponent 2. + conversion: + factor: 0.5424919 + base_units: + - '28' +- Status: '' + LevelAndCategory: '2' + Name: poundal per square inch + ConversionFactor: 2,142 957 × 10² Pa + Symbol: pdl/in² + CommonCode: N26 + Description: Non SI-conforming unit of the pressure according to the Imperial system + of units (poundal by square inch). + conversion: + factor: 2.142957 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: foot to the fourth power + ConversionFactor: 8,630 975 x 10⁻³ m⁴ + Symbol: ft⁴ + CommonCode: N27 + Description: 'Power of the unit foot according to the Anglo-American and Imperial + system of units by exponent 4 according to NIST: 1 ft4 = 8,630 975 m4.' + conversion: + factor: 0.008630975 + base_units: + - B83 +- Status: '' + LevelAndCategory: 1M + Name: cubic decimetre per kilogram + ConversionFactor: 10⁻³ m³ x kg⁻¹ + Symbol: dm³/kg + CommonCode: N28 + Description: 0,001 fold of the power of the SI base unit meter by exponent 3 divided + by the SI based unit kilogram. + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: cubic foot per pound + ConversionFactor: 6,242 796 x 10⁻² m³/kg + Symbol: ft³/lb + CommonCode: N29 + Description: Power of the unit foot according to the Anglo-American and Imperial + system of units by exponent 3 divided by the unit avoirdupois pound according + to the avoirdupois unit system. + conversion: + factor: 0.06242796 + base_units: + - A39 +- Status: '' + LevelAndCategory: '3.5' + Name: print point + ConversionFactor: 0,013 8 in (approx) + Symbol: '' + CommonCode: N3 + Description: '' + conversion: + factor: 0.0138 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: cubic inch per pound + ConversionFactor: 3,612 728 x 10⁻⁵ m³/kg + Symbol: in³/lb + CommonCode: N30 + Description: Power of the unit inch according to the Anglo-American and Imperial + system of units by exponent 3 divided by the avoirdupois pound according to the + avoirdupois unit system . + conversion: + factor: 3.612728e-05 + base_units: + - A39 +- Status: '' + LevelAndCategory: 1M + Name: kilonewton per metre + ConversionFactor: 10³ N/m + Symbol: kN/m + CommonCode: N31 + Description: 1000-fold of the derived SI unit newton divided by the SI base unit + metre. + conversion: + factor: 1000.0 + base_units: + - 4P +- Status: '' + LevelAndCategory: '2.0' + Name: poundal per inch + ConversionFactor: 5,443 110 N/m + Symbol: pdl/in + CommonCode: N32 + Description: Non SI-conforming unit of the surface tension according to the Imperial + unit system as quotient poundal by inch. + conversion: + factor: 5.44311 + base_units: + - 4P +- Status: '' + LevelAndCategory: '2.0' + Name: pound-force per yard + ConversionFactor: 4,864 635 N/m + Symbol: lbf/yd + CommonCode: N33 + Description: Unit of force per unit length based on the Anglo-American system of + units. + conversion: + factor: 4.864635 + base_units: + - 4P +- Status: '' + LevelAndCategory: '2.0' + Name: poundal second per square foot + ConversionFactor: 1,488 164 Pa x s + Symbol: "(pdl/ft²)·s" + CommonCode: N34 + Description: Non SI-conforming unit of viscosity. + conversion: + factor: 1.488164 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2' + Name: poise per pascal + ConversionFactor: 0,1 s + Symbol: P/Pa + CommonCode: N35 + Description: CGS (Centimetre-Gram-Second system) unit poise divided by the derived + SI unit pascal. + conversion: + factor: 0.1 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: 1S + Name: newton second per square metre + ConversionFactor: Pa x s + Symbol: "(N/m²)·s" + CommonCode: N36 + Description: Unit of the dynamic viscosity as a product of unit of the pressure + (newton by square metre) multiplied with the SI base unit second. + conversion: + factor: 1.0 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '1.0' + Name: kilogram per metre second + ConversionFactor: Pa x s + Symbol: kg/(m·s) + CommonCode: N37 + Description: Unit of the dynamic viscosity as a quotient SI base unit kilogram divided + by the SI base unit metre and by the SI base unit second. + conversion: + factor: 1.0 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '1.0' + Name: kilogram per metre minute + ConversionFactor: 1,666 67 × 10⁻² Pa x s + Symbol: kg/(m·min) + CommonCode: N38 + Description: Unit of the dynamic viscosity as a quotient SI base unit kilogram divided + by the SI base unit metre and by the unit minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per metre day + ConversionFactor: 1,157 41 × 10⁻⁵ Pa x s + Symbol: kg/(m·d) + CommonCode: N39 + Description: Unit of the dynamic viscosity as a quotient SI base unit kilogram divided + by the SI base unit metre and by the unit day. + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilogram per metre hour + ConversionFactor: 2,777 78 × 10⁻⁴ Pa x s + Symbol: kg/(m·h) + CommonCode: N40 + Description: Unit of the dynamic viscosity as a quotient SI base unit kilogram divided + by the SI base unit metre and by the unit hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gram per centimetre second + ConversionFactor: 0,1 Pa x s + Symbol: g/(cm·s) + CommonCode: N41 + Description: Unit of the dynamic viscosity as a quotient of the 0,001-fold of the + SI base unit kilogram divided by the 0,01-fold of the SI base unit metre and SI + base unit second. + conversion: + factor: 0.1 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2.0' + Name: poundal second per square inch + ConversionFactor: 2,142 957 x 10² Pa x s + Symbol: "(pdl/in²)·s" + CommonCode: N42 + Description: Non SI-conforming unit of dynamic viscosity according to the Imperial + system of units as product unit of the pressure (poundal by square inch) multiplied + by the SI base unit second. + conversion: + factor: 214.2957 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2.0' + Name: pound per foot minute + ConversionFactor: 2,480 273 x 10⁻² Pa x s + Symbol: lb/(ft·min) + CommonCode: N43 + Description: Unit of the dynamic viscosity according to the Anglo-American unit + system. + conversion: + factor: 0.02480273 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: '2.0' + Name: pound per foot day + ConversionFactor: 1,722 412 x 10⁻⁵ Pa x s + Symbol: lb/(ft·d) + CommonCode: N44 + Description: Unit of the dynamic viscosity according to the Anglo-American unit + system. + conversion: + factor: 1.722412e-05 + base_units: + - C65 + - N36 + - N37 +- Status: '' + LevelAndCategory: 1S + Name: cubic metre per second pascal + ConversionFactor: kg⁻¹ x m⁴ x s + Symbol: "(m³/s)/Pa" + CommonCode: N45 + Description: Power of the SI base unit meter by exponent 3 divided by the product + of the SI base unit second and the derived SI base unit pascal. + conversion: + factor: 1.0 + base_units: + - N45 +- Status: '' + LevelAndCategory: '2' + Name: foot poundal + ConversionFactor: 4,214 011 x 10⁻² J + Symbol: ft·pdl + CommonCode: N46 + Description: Unit of the work (force-path). + conversion: + factor: 0.04214011 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2.0' + Name: inch poundal + ConversionFactor: 3,511 677 x 10⁻³ J + Symbol: in·pdl + CommonCode: N47 + Description: Unit of work (force multiplied by path) according to the Imperial system + of units as a product unit inch multiplied by poundal. + conversion: + factor: 0.003511677 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2.0' + Name: watt per square centimetre + ConversionFactor: 10⁴ W/m² + Symbol: W/cm² + CommonCode: N48 + Description: Derived SI unit watt divided by the power of the 0,01-fold the SI base + unit metre by exponent 2. + conversion: + factor: 10000.0 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: watt per square inch + ConversionFactor: 1,550 003 x 10³ W/m² + Symbol: W/in² + CommonCode: N49 + Description: Derived SI unit watt divided by the power of the unit inch according + to the Anglo-American and Imperial system of units by exponent 2. + conversion: + factor: 1550.003 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per square foot hour + ConversionFactor: 3,154 591 W/m² + Symbol: BtuIT/(ft²·h) + CommonCode: N50 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 3.154591 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per square foot hour + ConversionFactor: 3,152 481 W/m² + Symbol: Btuth/(ft²·h) + CommonCode: N51 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 3.152481 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per square foot minute + ConversionFactor: 1,891 489 x 10² W/m² + Symbol: 'Btuth/(ft²·min) ' + CommonCode: N52 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 189.1489 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per square foot second + ConversionFactor: 1,135 653 x 10⁴ W/m² + Symbol: BtuIT/(ft²·s) + CommonCode: N53 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 11356.53 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per square foot second + ConversionFactor: 1,134 893 x 10⁴ W/m² + Symbol: Btuth/(ft²·s) + CommonCode: N54 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 11348.93 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per square inch second + ConversionFactor: 1,634 246 x 10⁶ W/m² + Symbol: BtuIT/(in²·s) + CommonCode: N55 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 1634246.0 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: calorie (thermochemical) per square centimetre minute + ConversionFactor: 6,973 333 x 10² W/m² + Symbol: calth/(cm²·min) + CommonCode: N56 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 697.3333 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: calorie (thermochemical) per square centimetre second + ConversionFactor: 4,184 x 10⁴ W/m² + Symbol: calth/(cm²·s) + CommonCode: N57 + Description: Unit of the surface heat flux according to the Imperial system of units. + conversion: + factor: 41840.0 + base_units: + - D54 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per cubic foot + ConversionFactor: 3,725 895 x10⁴ J/m³ + Symbol: BtuIT/ft³ + CommonCode: N58 + Description: Unit of the energy density according to the Imperial system of units. + conversion: + factor: 3.725895 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per cubic foot + ConversionFactor: 3,723 403 x10⁴ J/m³ + Symbol: Btuth/ft³ + CommonCode: N59 + Description: Unit of the energy density according to the Imperial system of units. + conversion: + factor: 3.723403 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per degree Fahrenheit + ConversionFactor: 1,899 101 x 10³ J/K + Symbol: BtuIT/ºF + CommonCode: N60 + Description: Unit of the heat capacity according to the Imperial system of units. + conversion: + factor: 1899.101 + base_units: + - JE +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per degree Fahrenheit + ConversionFactor: 1,897 830 x 10³ J/K + Symbol: Btuth/ºF + CommonCode: N61 + Description: Unit of the heat capacity according to the Imperial system of units. + conversion: + factor: 1897.83 + base_units: + - JE +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per degree Rankine + ConversionFactor: 1,899 101 x 10³ J/K + Symbol: BtuIT/ºR + CommonCode: N62 + Description: Unit of the heat capacity according to the Imperial system of units. + conversion: + factor: 1899.101 + base_units: + - JE +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per degree Rankine + ConversionFactor: 1,897 830 x 10³ J/K + Symbol: Btuth/ºR + CommonCode: N63 + Description: Unit of the heat capacity according to the Imperial system of units. + conversion: + factor: 1897.83 + base_units: + - JE +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per pound degree Rankine + ConversionFactor: 4,184 x 10³ J/(kg x K) + Symbol: "(Btuth/°R)/lb" + CommonCode: N64 + Description: Unit of the heat capacity (British thermal unit according to the international + table according to the Rankine degree) according to the Imperial system of units + divided by the unit avoirdupois pound according to the avoirdupois system of units. + conversion: + factor: 4184.0 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: kilocalorie (international table) per gram kelvin + ConversionFactor: 4,186 8 x 10⁶ J/(kg x K) + Symbol: "(kcalIT/K)/g" + CommonCode: N65 + Description: Unit of the mass-related heat capacity as quotient 1000-fold of the + calorie (international table) divided by the product of the 0,001-fold of the + SI base units kilogram and kelvin. + conversion: + factor: 4186800.0 + base_units: + - B11 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (39 ºF) + ConversionFactor: 1,059 67 x 10³ J + Symbol: 'Btu (39 ºF) ' + CommonCode: N66 + Description: Unit of heat energy according to the Imperial system of units in a + reference temperature of 39 °F. + conversion: + factor: 1059.67 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (59 ºF) + ConversionFactor: 1,054 80 x 10³ J + Symbol: Btu (59 ºF) + CommonCode: N67 + Description: Unit of heat energy according to the Imperial system of units in a + reference temperature of 59 °F. + conversion: + factor: 1054.8 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (60 ºF) + ConversionFactor: 1,054 68 x 10³ J + Symbol: 'Btu (60 ºF) ' + CommonCode: N68 + Description: Unit of head energy according to the Imperial system of units at a + reference temperature of 60 °F. + conversion: + factor: 1054.68 + base_units: + - JOU +- Status: '' + LevelAndCategory: '2' + Name: calorie (20 ºC) + ConversionFactor: 4,181 90 + Symbol: cal₂₀ + CommonCode: N69 + Description: Unit for quantity of heat, which is to be required for 1 g air free + water at a constant pressure from 101,325 kPa, to warm up the pressure of standard + atmosphere at sea level, from 19,5 °C on 20,5 °C. + conversion: + factor: 4.1819 +- Status: '' + LevelAndCategory: '2' + Name: quad (1015 BtuIT) + ConversionFactor: 1,055 056 × 10¹⁸ J + Symbol: quad + CommonCode: N70 + Description: Unit of heat energy according to the imperial system of units. + conversion: + factor: 1.055056 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: therm (EC) + ConversionFactor: 1,055 06 × 10⁸ J + Symbol: thm (EC) + CommonCode: N71 + Description: 'Unit of heat energy in commercial use, within the EU defined: 1 thm + (EC) = 100 000 BtuIT.' + conversion: + factor: 1.05506 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: therm (U.S.) + ConversionFactor: 1,054 804 × 10⁸ J + Symbol: thm (US) + CommonCode: N72 + Description: Unit of heat energy in commercial use. + conversion: + factor: 1.054804 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per pound + ConversionFactor: 2,324 444 x 10³ J/kg + Symbol: Btuth/lb + CommonCode: N73 + Description: Unit of the heat energy according to the Imperial system of units divided + the unit avoirdupois pound according to the avoirdupois system of units. + conversion: + factor: 2324.444 + base_units: + - J2 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per hour square foot degree Fahrenheit + ConversionFactor: 5,678 263 W/(m² x K) + Symbol: BtuIT/(h·ft²·ºF) + CommonCode: N74 + Description: Unit of the heat transition coefficient according to the Imperial system + of units. + conversion: + factor: 5.678263 + base_units: + - D55 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per hour square foot degree Fahrenheit + ConversionFactor: 5,674 466 W/(m² x K) + Symbol: Btuth/(h·ft²·ºF) + CommonCode: N75 + Description: Unit of the heat transition coefficient according to the imperial system + of units. + conversion: + factor: 5.674466 + base_units: + - D55 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (international table) per second square foot degree Fahrenheit + ConversionFactor: 2,044 175 x 10⁴ W/(m² x K) + Symbol: BtuIT/(s·ft²·ºF) + CommonCode: N76 + Description: Unit of the heat transition coefficient according to the imperial system + of units. + conversion: + factor: 20441.75 + base_units: + - D55 +- Status: '' + LevelAndCategory: '2' + Name: British thermal unit (thermochemical) per second square foot degree Fahrenheit + ConversionFactor: 2,042 808 x 10⁴ W/(m² x K) + Symbol: 'Btuth/(s·ft²·ºF) ' + CommonCode: N77 + Description: Unit of the heat transition coefficient according to the imperial system + of units. + conversion: + factor: 20428.08 + base_units: + - D55 +- Status: '' + LevelAndCategory: 1M + Name: kilowatt per square metre kelvin + ConversionFactor: 10³ W/(m² x K) + Symbol: kW/(m²·K) + CommonCode: N78 + Description: 1000-fold of the derived SI unit watt divided by the product of the + power of the SI base unit metre by exponent 2 and the SI base unit kelvin. + conversion: + factor: 1000.0 + base_units: + - D55 +- Status: '' + LevelAndCategory: 1S + Name: kelvin per pascal + ConversionFactor: kg⁻¹ x m x s² x K + Symbol: K/Pa + CommonCode: N79 + Description: SI base unit kelvin divided by the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - N79 +- Status: '' + LevelAndCategory: 1M + Name: watt per metre degree Celsius + ConversionFactor: W/(m x K) + Symbol: W/(m·°C) + CommonCode: N80 + Description: Derived SI unit watt divided by the product of the SI base unit metre + and the unit for temperature degree Celsius. + conversion: + factor: 1.0 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: 1M + Name: kilowatt per metre kelvin + ConversionFactor: 10³ W/(m x K) + Symbol: kW/(m·K) + CommonCode: N81 + Description: 1000-fold of the derived SI unit watt divided by the product of the + SI base unit metre and the SI base unit kelvin. + conversion: + factor: 1000.0 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: 1M + Name: kilowatt per metre degree Celsius + ConversionFactor: 10³ W/(m x K) + Symbol: kW/(m·°C) + CommonCode: N82 + Description: 1000-fold of the derived SI unit watt divided by the product of the + SI base unit metre and the unit for temperature degree Celsius. + conversion: + factor: 1000.0 + base_units: + - D53 + - N80 +- Status: '' + LevelAndCategory: '2.0' + Name: metre per degree Celcius metre + ConversionFactor: K⁻¹ + Symbol: m/(°C·m) + CommonCode: N83 + Description: SI base unit metre divided by the product of the unit degree Celsius + and the SI base unit metre. + conversion: + factor: 1.0 + base_units: + - C91 + - N83 +- Status: '' + LevelAndCategory: '2.0' + Name: degree Fahrenheit hour per British thermal unit (international table) + ConversionFactor: 1,895 634 K/W + Symbol: ºF/(BtuIT/h) + CommonCode: N84 + Description: Non SI-conforming unit of the thermal resistance according to the Imperial + system of units. + conversion: + factor: 1.895634 + base_units: + - B21 +- Status: '' + LevelAndCategory: '2.0' + Name: degree Fahrenheit hour per British thermal unit (thermochemical) + ConversionFactor: 1,896 903 K/W + Symbol: ºF/(Btuth/h) + CommonCode: N85 + Description: Non SI-conforming unit of the thermal resistance according to the Imperial + system of units. + conversion: + factor: 1.896903 + base_units: + - B21 +- Status: '' + LevelAndCategory: '2.0' + Name: degree Fahrenheit second per British thermal unit (international table) + ConversionFactor: 5,265 651 x 10⁻⁴ K/W + Symbol: ºF/(BtuIT/s) + CommonCode: N86 + Description: Non SI-conforming unit of the thermal resistance according to the Imperial + system of units. + conversion: + factor: 0.0005265651 + base_units: + - B21 +- Status: '' + LevelAndCategory: '2.0' + Name: degree Fahrenheit second per British thermal unit (thermochemical) + ConversionFactor: 5,269 175 x 10⁻⁴ K/W + Symbol: ºF/(Btuth/s) + CommonCode: N87 + Description: Non SI-conforming unit of the thermal resistance according to the Imperial + system of units. + conversion: + factor: 0.0005269175 + base_units: + - B21 +- Status: '' + LevelAndCategory: '2.0' + Name: degree Fahrenheit hour square foot per British thermal unit (international + table) inch + ConversionFactor: 6,933 472 K x m/W + Symbol: ºF·h·ft²/(BtuIT·in) + CommonCode: N88 + Description: Unit of specific thermal resistance according to the Imperial system + of units. + conversion: + factor: 6.933472 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: degree Fahrenheit hour square foot per British thermal unit (thermochemical) + inch + ConversionFactor: 6,938 112 K x m/W + Symbol: ºF·h·ft²/(Btuth·in) + CommonCode: N89 + Description: Unit of specific thermal resistance according to the Imperial system + of units. + conversion: + factor: 6.938112 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilofarad + ConversionFactor: 10³ F + Symbol: kF + CommonCode: N90 + Description: 1000-fold of the derived SI unit farad. + conversion: + factor: 1000.0 + base_units: + - FAR +- Status: '' + LevelAndCategory: '1.0' + Name: reciprocal joule + ConversionFactor: 1/J + Symbol: 1/J + CommonCode: N91 + Description: Reciprocal of the derived SI unit joule. + conversion: + factor: 1.0 + base_units: + - N91 +- Status: '' + LevelAndCategory: 1M + Name: picosiemens + ConversionFactor: 10⁻¹² S + Symbol: pS + CommonCode: N92 + Description: 0,000 000 000 001-fold of the derived SI unit siemens. + conversion: + factor: 1.0e-12 + base_units: + - NQ +- Status: '' + LevelAndCategory: 1M + Name: ampere per pascal + ConversionFactor: kg⁻¹ x m x s² x A + Symbol: A/Pa + CommonCode: N93 + Description: SI base unit ampere divided by the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - N93 +- Status: '' + LevelAndCategory: '2.0' + Name: franklin + ConversionFactor: 3,335 641 x 10⁻¹⁰ C + Symbol: Fr + CommonCode: N94 + Description: CGS (Centimetre-Gram-Second system) unit of the electrical charge, + where the charge amounts to exactly 1 Fr where the force of 1 dyn on an equal + load is performed at a distance of 1 cm. + conversion: + factor: 3.335641e-10 + base_units: + - A8 +- Status: '' + LevelAndCategory: 1M + Name: ampere minute + ConversionFactor: 60 C + Symbol: A·min + CommonCode: N95 + Description: A unit of electric charge defining the amount of charge accumulated + by a steady flow of one ampere for one minute.. + conversion: + factor: 60.0 + base_units: + - A8 +- Status: '' + LevelAndCategory: '2.0' + Name: biot + ConversionFactor: 10¹ A + Symbol: Bi + CommonCode: N96 + Description: CGS (Centimetre-Gram-Second system) unit of the electric power which + is defined by a force of 2 dyn per cm between two parallel conductors of infinite + length with negligible cross-section in the distance of 1 cm. + conversion: + factor: 10.0 + base_units: + - AMP +- Status: '' + LevelAndCategory: '2.0' + Name: gilbert + ConversionFactor: 7,957 747 x 10⁻¹ A + Symbol: Gi + CommonCode: N97 + Description: CGS (Centimetre-Gram-Second system) unit of the magnetomotive force, + which is defined by the work to increase the magnetic potential of a positive + common pol with 1 erg. + conversion: + factor: 0.7957747 + base_units: + - AMP +- Status: '' + LevelAndCategory: 1M + Name: volt per pascal + ConversionFactor: m³ x s⁻¹ x A⁻¹ + Symbol: V/Pa + CommonCode: N98 + Description: Derived SI unit volt divided by the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - N98 +- Status: '' + LevelAndCategory: 1M + Name: picovolt + ConversionFactor: 10⁻¹² V + Symbol: pV + CommonCode: N99 + Description: 0,000 000 000 001-fold of the derived SI unit volt. + conversion: + factor: 1.0e-12 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: 1S + Name: milligram per kilogram + ConversionFactor: 10⁻⁶ 1 + Symbol: mg/kg + CommonCode: NA + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: number of articles + ConversionFactor: '' + Symbol: '' + CommonCode: NAR + Description: 'A unit of count defining the number of articles (article: item).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: barge + ConversionFactor: '' + Symbol: '' + CommonCode: NB + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.7' + Name: number of bobbins + ConversionFactor: '' + Symbol: '' + CommonCode: NBB + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: car + ConversionFactor: '' + Symbol: '' + CommonCode: NC + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: number of cells + ConversionFactor: '' + Symbol: '' + CommonCode: NCL + Description: 'A unit of count defining the number of cells (cell: an enclosed or + circumscribed space, cavity, or volume).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: net barrel + ConversionFactor: '' + Symbol: '' + CommonCode: ND + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: net litre + ConversionFactor: '' + Symbol: '' + CommonCode: NE + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: newton + ConversionFactor: "(kg x m)/s²" + Symbol: N + CommonCode: NEW + Description: '' + conversion: + factor: 1.0 + base_units: + - M77 + - NEW +- Status: '' + LevelAndCategory: '3.9' + Name: message + ConversionFactor: '' + Symbol: '' + CommonCode: NF + Description: A unit of count defining the number of messages. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: net gallon (us) + ConversionFactor: '' + Symbol: '' + CommonCode: NG + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: message hour + ConversionFactor: '' + Symbol: '' + CommonCode: NH + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: net imperial gallon + ConversionFactor: '' + Symbol: '' + CommonCode: NI + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: nil + ConversionFactor: '' + Symbol: "()" + CommonCode: NIL + Description: A unit of count defining the number of instances of nothing. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: number of international units + ConversionFactor: '' + Symbol: '' + CommonCode: NIU + Description: A unit of count defining the number of international units. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.7' + Name: number of screens + ConversionFactor: '' + Symbol: '' + CommonCode: NJ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: load + ConversionFactor: '' + Symbol: '' + CommonCode: NL + Description: 'A unit of volume defining the number of loads (load: a quantity of + items carried or processed at one time).' + conversion: + factor: 1.0 +- Status: "¦" + LevelAndCategory: '2' + Name: Normalised cubic metre + ConversionFactor: m3 + Symbol: '' + CommonCode: NM3 + Description: Normalised cubic metre (temperature 0°C and pressure 1013.25 millibars + ) + conversion: + factor: 1.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '1' + Name: nautical mile + ConversionFactor: 1 852 m + Symbol: n mile + CommonCode: NMI + Description: '' + conversion: + factor: 1852.0 + base_units: + - MTR +- Status: '' + LevelAndCategory: '3.7' + Name: number of packs + ConversionFactor: '' + Symbol: '' + CommonCode: NMP + Description: 'A unit of count defining the number of packs (pack: a collection of + objects packaged together).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: train + ConversionFactor: '' + Symbol: '' + CommonCode: NN + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.7' + Name: number of parcels + ConversionFactor: '' + Symbol: '' + CommonCode: NPL + Description: '' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.7' + Name: number of pairs + ConversionFactor: use pair + Symbol: '' + CommonCode: NPR + Description: 'A unit of count defining the number of pairs (pair: item described + by two''s).' + conversion: + factor: 1.0 + base_units: + - NPR +- Status: '' + LevelAndCategory: '3.7' + Name: number of parts + ConversionFactor: '' + Symbol: '' + CommonCode: NPT + Description: 'A unit of count defining the number of parts (part: component of a + larger entity).' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: mho + ConversionFactor: S + Symbol: '' + CommonCode: NQ + Description: '' + conversion: + factor: 1.0 + base_units: + - NQ +- Status: D + LevelAndCategory: '2' + Name: micromho + ConversionFactor: 10⁻⁶ S + Symbol: '' + CommonCode: NR + Description: '' + conversion: + factor: 1.0e-06 + base_units: + - NQ +- Status: X + LevelAndCategory: '3.7' + Name: number of rolls + ConversionFactor: '' + Symbol: '' + CommonCode: NRL + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: net ton + ConversionFactor: '' + Symbol: '' + CommonCode: NT + Description: A unit of mass equal to 2000 pounds, see ton (US). Refer International + Convention on tonnage measurement of Ships. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.4' + Name: net register ton + ConversionFactor: '' + Symbol: '' + CommonCode: NTT + Description: A unit of mass equal to the total cubic footage after deductions, where + 1 register ton is equal to 100 cubic feet. Refer International Convention on tonnage + measurement of Ships. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: newton metre + ConversionFactor: N x m + Symbol: N·m + CommonCode: NU + Description: '' + conversion: + factor: 1.0 + base_units: + - NU +- Status: X + LevelAndCategory: '3.4' + Name: vehicle + ConversionFactor: '' + Symbol: '' + CommonCode: NV + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: part per thousand + ConversionFactor: 1 x 10⁻³ + Symbol: "‰" + CommonCode: NX + Description: 'A unit of proportion equal to 10⁻³. ,Synonym: per mille' + conversion: + factor: 0.001 +- Status: X + LevelAndCategory: '3.5' + Name: pound per air dry metric ton + ConversionFactor: '' + Symbol: '' + CommonCode: NY + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: panel + ConversionFactor: '' + Symbol: '' + CommonCode: OA + Description: 'A unit of count defining the number of panels (panel: a distinct, + usually rectangular, section of a surface).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: ozone depletion equivalent + ConversionFactor: '' + Symbol: '' + CommonCode: ODE + Description: A unit of mass defining the ozone depletion potential in kilograms + of a product relative to the calculated depletion for the reference substance, + Trichlorofluoromethane (CFC-11). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: ODS Grams + ConversionFactor: '' + Symbol: '' + CommonCode: ODG + Description: A unit of measure calculated by multiplying the mass of the substance + in grams and the ozone-depleting potential for the substance., + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: ODS Kilograms + ConversionFactor: '' + Symbol: '' + CommonCode: ODK + Description: A unit of measure calculated by multiplying the mass of the substance + in kilograms and the ozone-depleting potential for the substance. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: ODS Milligrams + ConversionFactor: '' + Symbol: '' + CommonCode: ODM + Description: A unit of measure calculated by multiplying the mass of the substance + in milligrams and the ozone-depleting potential for the substance. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: ohm + ConversionFactor: Ω + Symbol: Ω + CommonCode: OHM + Description: '' + conversion: + factor: 1.0 + base_units: + - OHM +- Status: '' + LevelAndCategory: '2' + Name: ounce per square yard + ConversionFactor: 3,390 575 x 10⁻² kg/m² + Symbol: oz/yd² + CommonCode: 'ON' + Description: '' + conversion: + factor: 0.03390575 + base_units: + - '28' +- Status: '' + LevelAndCategory: '2' + Name: ounce (avoirdupois) + ConversionFactor: 2,834 952 x 10⁻² kg + Symbol: oz + CommonCode: ONZ + Description: '' + conversion: + factor: 0.02834952 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.2' + Name: two pack + ConversionFactor: '' + Symbol: '' + CommonCode: OP + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: oscillations per minute + ConversionFactor: 1.667 x 10-2 /s + Symbol: o/min + CommonCode: OPM + Description: The number of oscillations per minute. + conversion: + factor: 1.0 + base_units: + - BPM + - OPM +- Status: '' + LevelAndCategory: '3.1' + Name: overtime hour + ConversionFactor: '' + Symbol: '' + CommonCode: OT + Description: A unit of time defining the number of overtime hours. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.1' + Name: ounce av + ConversionFactor: '' + Symbol: '' + CommonCode: OZ + Description: A unit of measure equal to 1/16 of a pound or about 28.3495 grams (av + = avoirdupois). Use ounce (common code ONZ). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: fluid ounce (US) + ConversionFactor: 2,957 353 x 10⁻⁵ m³ + Symbol: fl oz (US) + CommonCode: OZA + Description: '' + conversion: + factor: 2.957353e-05 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: fluid ounce (UK) + ConversionFactor: 2,841 306 x 10⁻⁵ m³ + Symbol: fl oz (UK) + CommonCode: OZI + Description: '' + conversion: + factor: 2.841306e-05 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.9' + Name: page - electronic + ConversionFactor: '' + Symbol: '' + CommonCode: P0 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: percent + ConversionFactor: 1 x 10⁻² + Symbol: "% or pct" + CommonCode: P1 + Description: A unit of proportion equal to 0.01. + conversion: + factor: 0.01 +- Status: '' + LevelAndCategory: '1.0' + Name: coulomb per metre + ConversionFactor: m⁻¹ x s x A + Symbol: C/m + CommonCode: P10 + Description: Derived SI unit coulomb divided by the SI base unit metre. + conversion: + factor: 1.0 + base_units: + - P10 +- Status: '' + LevelAndCategory: 1M + Name: kiloweber + ConversionFactor: 10³ Wb + Symbol: kWb + CommonCode: P11 + Description: 1000 fold of the derived SI unit weber. + conversion: + factor: 1000.0 + base_units: + - WEB +- Status: '' + LevelAndCategory: '2.0' + Name: gamma + ConversionFactor: 10⁻⁹ T + Symbol: γ + CommonCode: P12 + Description: Unit of magnetic flow density. + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilotesla + ConversionFactor: 10³ T + Symbol: kT + CommonCode: P13 + Description: 1000-fold of the derived SI unit tesla. + conversion: + factor: 1000.0 + base_units: + - D33 +- Status: '' + LevelAndCategory: '1.0' + Name: joule per second + ConversionFactor: W + Symbol: J/s + CommonCode: P14 + Description: Quotient of the derived SI unit joule divided by the SI base unit second. + conversion: + factor: 1.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1M + Name: joule per minute + ConversionFactor: 1,666 67 × 10⁻² W + Symbol: J/min + CommonCode: P15 + Description: Quotient from the derived SI unit joule divided by the unit minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: joule per hour + ConversionFactor: 2,777 78 × 10⁻⁴ W + Symbol: J/h + CommonCode: P16 + Description: Quotient from the derived SI unit joule divided by the unit hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: joule per day + ConversionFactor: 1,157 41 × 10⁻⁵ W + Symbol: J/d + CommonCode: P17 + Description: Quotient from the derived SI unit joule divided by the unit day. + conversion: + factor: 1.15741 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilojoule per second + ConversionFactor: 10³ W + Symbol: kJ/s + CommonCode: P18 + Description: Quotient from the 1000-fold of the derived SI unit joule divided by + the SI base unit second. + conversion: + factor: 1000.0 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1M + Name: kilojoule per minute + ConversionFactor: 1,666 67 × 10 W + Symbol: kJ/min + CommonCode: P19 + Description: Quotient from the 1000-fold of the derived SI unit joule divided by + the unit minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: '2' + Name: pound per foot + ConversionFactor: 1,488 164 kg/m + Symbol: lb/ft + CommonCode: P2 + Description: '' + conversion: + factor: 1.488164 + base_units: + - KL +- Status: '' + LevelAndCategory: 1M + Name: kilojoule per hour + ConversionFactor: 2,777 78 x 10⁻¹ W + Symbol: kJ/h + CommonCode: P20 + Description: Quotient from the 1000-fold of the derived SI unit joule divided by + the unit hour. + conversion: + factor: 0.277778 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1M + Name: kilojoule per day + ConversionFactor: 1,157 41 x 10⁻² W + Symbol: kJ/d + CommonCode: P21 + Description: Quotient from the 1000-fold of the derived SI unit joule divided by + the unit day. + conversion: + factor: 0.0115741 + base_units: + - D46 + - P14 + - WTT +- Status: '' + LevelAndCategory: 1M + Name: nanoohm + ConversionFactor: 10⁻⁹ Ω + Symbol: nΩ + CommonCode: P22 + Description: 0,000 000 001-fold of the derived SI unit ohm. + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: ohm circular-mil per foot + ConversionFactor: 1,662 426 x 10⁻⁹ Ω x m + Symbol: 'Ω·cmil/ft ' + CommonCode: P23 + Description: Unit of resistivity. + conversion: + factor: 1.662426e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilohenry + ConversionFactor: 10³ H + Symbol: kH + CommonCode: P24 + Description: 1000-fold of the derived SI unit henry. + conversion: + factor: 1000.0 + base_units: + - '81' +- Status: '' + LevelAndCategory: '2.0' + Name: lumen per square foot + ConversionFactor: 1,076 391 x 10¹ cd x sr / m² + Symbol: lm/ft² + CommonCode: P25 + Description: Derived SI unit lumen divided by the power of the unit foot according + to the Anglo-American and Imperial system of units by exponent 2. + conversion: + factor: 10.76391 + base_units: + - LUX +- Status: '' + LevelAndCategory: '2.0' + Name: phot + ConversionFactor: 10⁴ cd x sr / m² + Symbol: ph + CommonCode: P26 + Description: CGS (Centimetre-Gram-Second system) unit of luminance, defined as lumen + by square centimetre. + conversion: + factor: 10000.0 + base_units: + - LUX +- Status: '' + LevelAndCategory: '2.0' + Name: footcandle + ConversionFactor: 1,076 391 x 10¹ cd x sr / m² + Symbol: ftc + CommonCode: P27 + Description: Non SI conform traditional unit, defined as density of light which + impinges on a surface which has a distance of one foot from a light source, which + shines with an intensity of an international candle. + conversion: + factor: 10.76391 + base_units: + - LUX +- Status: '' + LevelAndCategory: '2.0' + Name: candela per square inch + ConversionFactor: 1,550 003 x 10³ cd/m² + Symbol: cd/in² + CommonCode: P28 + Description: SI base unit candela divided by the power of unit inch according to + the Anglo-American and Imperial system of units by exponent 2. + conversion: + factor: 1550.003 + base_units: + - A24 +- Status: '' + LevelAndCategory: '2.0' + Name: footlambert + ConversionFactor: 3,426 259 cd/m² + Symbol: ftL + CommonCode: P29 + Description: Unit of the luminance according to the Anglo-American system of units, + defined as emitted or reflected luminance of a lm/ft². + conversion: + factor: 3.426259 + base_units: + - A24 +- Status: X + LevelAndCategory: '3.2' + Name: three pack + ConversionFactor: '' + Symbol: '' + CommonCode: P3 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2.0' + Name: lambert + ConversionFactor: 3,183 099 x 10³ cd/m² + Symbol: Lb + CommonCode: P30 + Description: CGS (Centimetre-Gram-Second system) unit of luminance, defined as the + emitted or reflected luminance by one lumen per square centimetre. + conversion: + factor: 3183.099 + base_units: + - A24 +- Status: '' + LevelAndCategory: '2.0' + Name: stilb + ConversionFactor: 10⁴ cd/m² + Symbol: sb + CommonCode: P31 + Description: CGS (Centimetre-Gram-Second system) unit of luminance, defined as emitted + or reflected luminance by one lumen per square centimetre. + conversion: + factor: 10000.0 + base_units: + - A24 +- Status: '' + LevelAndCategory: '2.0' + Name: candela per square foot + ConversionFactor: 1,076 391 x 10 cd/m² + Symbol: cd/ft² + CommonCode: P32 + Description: Base unit SI candela divided by the power of the unit foot according + to the Anglo-American and Imperial system of units by exponent 2. + conversion: + factor: 1.076391 + base_units: + - A24 +- Status: '' + LevelAndCategory: 1M + Name: kilocandela + ConversionFactor: 10³ cd + Symbol: kcd + CommonCode: P33 + Description: 1000-fold of the SI base unit candela. + conversion: + factor: 1000.0 + base_units: + - CDL +- Status: '' + LevelAndCategory: 1M + Name: millicandela + ConversionFactor: 10⁻³ cd + Symbol: mcd + CommonCode: P34 + Description: 0,001-fold of the SI base unit candela. + conversion: + factor: 0.001 + base_units: + - CDL +- Status: '' + LevelAndCategory: '2.0' + Name: Hefner-Kerze + ConversionFactor: 0,903 cd + Symbol: HK + CommonCode: P35 + Description: 'Obsolete, non-legal unit of the power in Germany relating to DIN 1301-3:1979: + 1 HK = 0,903 cd.' + conversion: + factor: 0.903 + base_units: + - CDL +- Status: '' + LevelAndCategory: '2.0' + Name: international candle + ConversionFactor: 1,019 cd + Symbol: IK + CommonCode: P36 + Description: 'Obsolete, non-legal unit of the power in Germany relating to DIN 1301-3:1979: + 1 HK = 1,019 cd.' + conversion: + factor: 1.019 + base_units: + - CDL +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (international table) per square foot + ConversionFactor: 1,135 653 x 10⁴ J/m² + Symbol: BtuIT/ft² + CommonCode: P37 + Description: Unit of the areal-related energy transmission according to the Imperial + system of units. + conversion: + factor: 11356.53 + base_units: + - B13 +- Status: '' + LevelAndCategory: '2.0' + Name: British thermal unit (thermochemical) per square foot + ConversionFactor: 1,134 893 x 10⁴ J/m² + Symbol: Btuth/ft² + CommonCode: P38 + Description: Unit of the areal-related energy transmission according to the Imperial + system of units. + conversion: + factor: 11348.93 + base_units: + - B13 +- Status: '' + LevelAndCategory: '2.0' + Name: calorie (thermochemical) per square centimetre + ConversionFactor: 4,184 x 10⁴ J/m² + Symbol: calth/cm² + CommonCode: P39 + Description: Unit of the areal-related energy transmission according to the Imperial + system of units. + conversion: + factor: 41840.0 + base_units: + - B13 +- Status: X + LevelAndCategory: '3.2' + Name: four pack + ConversionFactor: '' + Symbol: '' + CommonCode: P4 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2.0' + Name: langley + ConversionFactor: 4,184 x 10⁴ J/m² + Symbol: Ly + CommonCode: P40 + Description: CGS (Centimetre-Gram-Second system) unit of the areal-related energy + transmission (as a measure of the incident quantity of heat of solar radiation + on the earth's surface). + conversion: + factor: 41840.0 + base_units: + - B13 +- Status: '' + LevelAndCategory: '2' + Name: decade (logarithmic) + ConversionFactor: dec + Symbol: dec + CommonCode: P41 + Description: 1 Dec := log2 10 ˜ 3,32 according to the logarithm for frequency range + between f1 and f2, when f2/f1 = 10. + conversion: + factor: 1.0 + base_units: + - P41 +- Status: '' + LevelAndCategory: '1.0' + Name: pascal squared second + ConversionFactor: m⁻² x kg² x s⁻³ + Symbol: Pa²·s + CommonCode: P42 + Description: Unit of the set as a product of the power of derived SI unit pascal + with exponent 2 and the SI base unit second. + conversion: + factor: 1.0 + base_units: + - P42 +- Status: '' + LevelAndCategory: 1M + Name: bel per metre + ConversionFactor: B/m + Symbol: B/m + CommonCode: P43 + Description: Unit bel divided by the SI base unit metre. + conversion: + factor: 1.0 + base_units: + - P43 +- Status: '' + LevelAndCategory: '2.0' + Name: pound mole + ConversionFactor: 453,592 4 mol + Symbol: lbmol + CommonCode: P44 + Description: Non SI-conforming unit of quantity of a substance relating that one + pound mole of a chemical composition corresponds to the same number of pounds + as the molecular weight of one molecule of this composition in atomic mass units. + conversion: + factor: 453.5924 + base_units: + - C34 +- Status: '' + LevelAndCategory: '2.0' + Name: pound mole per second + ConversionFactor: 4,535 924 x 10² mol/s + Symbol: lbmol/s + CommonCode: P45 + Description: Non SI-conforming unit of the power of the amount of substance non-SI + compliant unit of the molar flux relating that a pound mole of a chemical composition + the same number of pound corresponds like the molecular weight of a molecule of + this composition in atomic mass units. + conversion: + factor: 453.5924 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: pound mole per minute + ConversionFactor: 7,559 873 mol/s + Symbol: lbmol/h + CommonCode: P46 + Description: Non SI-conforming unit of the power of the amount of substance non-SI + compliant unit of the molar flux relating that a pound mole of a chemical composition + the same number of pound corresponds like the molecular weight of a molecule of + this composition in atomic mass units. + conversion: + factor: 7.559873 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilomole per kilogram + ConversionFactor: 10³ mol/kg + Symbol: kmol/kg + CommonCode: P47 + Description: 1000-fold of the SI base unit mol divided by the SI base unit kilogram. + conversion: + factor: 1000.0 + base_units: + - C19 +- Status: '' + LevelAndCategory: '2.0' + Name: pound mole per pound + ConversionFactor: 10³ mol/kg + Symbol: lbmol/lb + CommonCode: P48 + Description: Non SI-conforming unit of the material molar flux divided by the avoirdupois + pound for mass according to the avoirdupois unit system. + conversion: + factor: 1000.0 + base_units: + - C19 +- Status: '' + LevelAndCategory: 1S + Name: newton square metre per ampere + ConversionFactor: m³ x kg x s⁻² x A⁻¹ + Symbol: N·m²/A + CommonCode: P49 + Description: Product of the derived SI unit newton and the power of SI base unit + metre with exponent 2 divided by the SI base unit ampere. + conversion: + factor: 1.0 + base_units: + - P49 + - P50 +- Status: '' + LevelAndCategory: '3.2' + Name: five pack + ConversionFactor: '' + Symbol: '' + CommonCode: P5 + Description: 'A unit of count defining the number of five-packs (five-pack: set + of five items packaged together).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: weber metre + ConversionFactor: m³ x kg x s⁻² x A⁻¹ + Symbol: Wb·m + CommonCode: P50 + Description: Product of the derived SI unit weber and SI base unit metre. + conversion: + factor: 1.0 + base_units: + - P49 + - P50 +- Status: '' + LevelAndCategory: 1S + Name: mol per kilogram pascal + ConversionFactor: m x kg⁻² x s² x mol + Symbol: "(mol/kg)/Pa" + CommonCode: P51 + Description: SI base unit mol divided by the product of the SI base unit kilogram + and the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - P51 +- Status: '' + LevelAndCategory: 1S + Name: mol per cubic metre pascal + ConversionFactor: m⁻² x kg⁻¹ x s² x mol + Symbol: "(mol/m³)/Pa" + CommonCode: P52 + Description: SI base unit mol divided by the product of the power from the SI base + unit metre with exponent 3 and the derived SI unit pascal. + conversion: + factor: 1.0 + base_units: + - P52 +- Status: '' + LevelAndCategory: '2.0' + Name: unit pole + ConversionFactor: 1,256 637 x 10⁻⁷ Wb + Symbol: 'unit pole ' + CommonCode: P53 + Description: CGS (Centimetre-Gram-Second system) unit for magnetic flux of a magnetic + pole (according to the interaction of identical poles of 1 dyn at a distance of + a cm). + conversion: + factor: 1.256637e-07 + base_units: + - WEB +- Status: '' + LevelAndCategory: 1M + Name: milligray per second + ConversionFactor: 10⁻³ Gy/s + Symbol: mGy/s + CommonCode: P54 + Description: 0,001-fold of the derived SI unit gray divided by the SI base unit + second. + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: microgray per second + ConversionFactor: 10⁻⁶ Gy/s + Symbol: µGy/s + CommonCode: P55 + Description: 0,000 001-fold of the derived SI unit gray divided by the SI base unit + second. + conversion: + factor: 1.0e-06 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: nanogray per second + ConversionFactor: 10⁻⁹ Gy/s + Symbol: nGy/s + CommonCode: P56 + Description: 0,000 000 001-fold of the derived SI unit gray divided by the SI base + unit second. + conversion: + factor: 1.0e-09 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gray per minute + ConversionFactor: 1,666 67 × 10⁻² Gy/s + Symbol: Gy/min + CommonCode: P57 + Description: SI derived unit gray divided by the unit minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligray per minute + ConversionFactor: 1,666 67 × 10⁻⁵ Gy/s + Symbol: mGy/min + CommonCode: P58 + Description: 0,001-fold of the derived SI unit gray divided by the unit minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: microgray per minute + ConversionFactor: 1,666 67 × 10⁻⁸ Gy/s + Symbol: µGy/min + CommonCode: P59 + Description: 0,000 001-fold of the derived SI unit gray divided by the unit minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: X + LevelAndCategory: '3.2' + Name: six pack + ConversionFactor: '' + Symbol: '' + CommonCode: P6 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: nanogray per minute + ConversionFactor: 1,666 67 × 10⁻¹¹ Gy/s + Symbol: nGy/min + CommonCode: P60 + Description: 0,000 000 001-fold of the derived SI unit gray divided by the unit + minute. + conversion: + factor: 1.66667 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: gray per hour + ConversionFactor: 2,777 78 × 10⁻⁴ Gy/s + Symbol: Gy/h + CommonCode: P61 + Description: SI derived unit gray divided by the unit hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: milligray per hour + ConversionFactor: 2,777 78 × 10⁻⁷ Gy/s + Symbol: mGy/h + CommonCode: P62 + Description: 0,001-fold of the derived SI unit gray divided by the unit hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: microgray per hour + ConversionFactor: 2,777 78 × 10⁻¹⁰ Gy/s + Symbol: µGy/h + CommonCode: P63 + Description: 0,000 001-fold of the derived SI unit gray divided by the unit hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: nanogray per hour + ConversionFactor: 2,777 78 × 10⁻¹³ Gy/s + Symbol: nGy/h + CommonCode: P64 + Description: 0,000 000 001-fold of the derived SI unit gray divided by the unit + hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: sievert per second + ConversionFactor: Sv/s + Symbol: Sv/s + CommonCode: P65 + Description: Derived SI unit sievert divided by the SI base unit second. + conversion: + factor: 1.0 + base_units: + - P65 +- Status: '' + LevelAndCategory: '2.0' + Name: millisievert per second + ConversionFactor: 10⁻³ Sv/s + Symbol: mSv/s + CommonCode: P66 + Description: 0,001-fold of the derived SI unit sievert divided by the SI base unit + second. + conversion: + factor: 0.001 + base_units: + - P65 +- Status: '' + LevelAndCategory: '2.0' + Name: microsievert per second + ConversionFactor: 10⁻⁶ Sv/s + Symbol: µSv/s + CommonCode: P67 + Description: 0,000 001-fold of the derived SI unit sievert divided by the SI base + unit second. + conversion: + factor: 1.0e-06 + base_units: + - P65 +- Status: '' + LevelAndCategory: '2.0' + Name: nanosievert per second + ConversionFactor: 10⁻⁹ Sv/s + Symbol: nSv/s + CommonCode: P68 + Description: 0,000 000 001-fold of the derived SI unit sievert divided by the SI + base unit second. + conversion: + factor: 1.0e-09 + base_units: + - P65 +- Status: '' + LevelAndCategory: '2.0' + Name: rem per second + ConversionFactor: 10⁻² Sv/s + Symbol: rem/s + CommonCode: P69 + Description: 'Unit for the equivalent tin rate relating to DIN 1301-3:1979: 1 rem/s + = 0,01 J/(kg·s) = 1 Sv/s.' + conversion: + factor: 0.01 + base_units: + - P65 +- Status: X + LevelAndCategory: '3.2' + Name: seven pack + ConversionFactor: '' + Symbol: '' + CommonCode: P7 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2.0' + Name: sievert per hour + ConversionFactor: 2,777 78 × 10⁻⁴ Sv/s + Symbol: Sv/h + CommonCode: P70 + Description: Derived SI unit sievert divided by the unit hour. + conversion: + factor: 2.77778 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: millisievert per hour + ConversionFactor: 0,277 777 778 × 10⁻⁷ Sv/s + Symbol: mSv/h + CommonCode: P71 + Description: 0,001-fold of the derived SI unit sievert divided by the unit hour. + conversion: + factor: 0.277777778 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: microsievert per hour + ConversionFactor: 0,277 777 778 × 10⁻¹⁰ Sv/s + Symbol: µSv/h + CommonCode: P72 + Description: 0,000 001-fold of the derived SI unit sievert divided by the unit hour. + conversion: + factor: 0.277777778 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: nanosievert per hour + ConversionFactor: 0,277 777 778 × 10⁻¹³ Sv/s + Symbol: nSv/h + CommonCode: P73 + Description: 0,000 000 001-fold of the derived SI unit sievert divided by the unit + hour. + conversion: + factor: 0.277777778 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: sievert per minute + ConversionFactor: 0,016 666 Sv/s + Symbol: Sv/min + CommonCode: P74 + Description: Derived SI unit sievert divided by the unit minute. + conversion: + factor: 0.016666 + base_units: + - P65 +- Status: '' + LevelAndCategory: '2.0' + Name: millisievert per minute + ConversionFactor: 1,666 666 667 × 10⁻⁵ Sv/s + Symbol: mSv/min + CommonCode: P75 + Description: 0,001-fold of the derived SI unit sievert divided by the unit minute. + conversion: + factor: 1.666666667 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: microsievert per minute + ConversionFactor: 1,666 666 667 × 10⁻⁸ Sv/s + Symbol: µSv/min + CommonCode: P76 + Description: 0,000 001-fold of the derived SI unit sievert divided by the unit minute. + conversion: + factor: 1.666666667 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: nanosievert per minute + ConversionFactor: 1,666 666 667 × 10⁻¹¹ Sv/s + Symbol: nSv/min + CommonCode: P77 + Description: 0,000 000 001-fold of the derived SI unit sievert divided by the unit + minute. + conversion: + factor: 1.666666667 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: reciprocal square inch + ConversionFactor: 1,550 003 x 10³ m⁻² + Symbol: 1/in² + CommonCode: P78 + Description: Complement of the power of the unit inch according to the Anglo-American + and Imperial system of units by exponent 2. + conversion: + factor: 1550.003 + base_units: + - C93 +- Status: '' + LevelAndCategory: '1.0' + Name: pascal square metre per kilogram + ConversionFactor: m/s² + Symbol: Pa/(kg/m²) + CommonCode: P79 + Description: Unit of the burst index as derived unit for pressure pascal related + to the substance, represented as a quotient from the SI base unit kilogram divided + by the power of the SI base unit metre by exponent 2. + conversion: + factor: 1.0 + base_units: + - MSK + - P79 +- Status: X + LevelAndCategory: '3.2' + Name: eight pack + ConversionFactor: '' + Symbol: '' + CommonCode: P8 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: millipascal per metre + ConversionFactor: 10⁻³ kg/(m² x s²) + Symbol: mPa/m + CommonCode: P80 + Description: 0,001-fold of the derived SI unit pascal divided by the SI base unit + metre. + conversion: + factor: 0.001 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: kilopascal per metre + ConversionFactor: 10³ kg/(m² x s²) + Symbol: kPa/m + CommonCode: P81 + Description: 1000-fold of the derived SI unit pascal divided by the SI base unit + metre. + conversion: + factor: 1000.0 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: hectopascal per metre + ConversionFactor: 10² kg/(m² x s²) + Symbol: hPa/m + CommonCode: P82 + Description: 100-fold of the derived SI unit pascal divided by the SI base unit + metre. + conversion: + factor: 100.0 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: standard atmosphere per metre + ConversionFactor: 1,013 25 x 10⁵ kg/(m² x s²) + Symbol: Atm/m + CommonCode: P83 + Description: Outdated unit of the pressure divided by the SI base unit metre. + conversion: + factor: 101325.0 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: technical atmosphere per metre + ConversionFactor: 9,806 65 x 10⁴ kg/(m² x s²) + Symbol: at/m + CommonCode: P84 + Description: Obsolete and non-legal unit of the pressure which is generated by a + 10 metre water column divided by the SI base unit metre. + conversion: + factor: 98066.5 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: torr per metre + ConversionFactor: 1,333 224 x 10² kg/(m² x s²) + Symbol: Torr/m + CommonCode: P85 + Description: CGS (Centimetre-Gram-Second system) unit of the pressure divided by + the SI base unit metre. + conversion: + factor: 133.3224 + base_units: [] +- Status: '' + LevelAndCategory: '2.0' + Name: psi per inch + ConversionFactor: 2,714 471 x 10⁵ kg/(m² x s²) + Symbol: psi/in + CommonCode: P86 + Description: Compound unit for pressure (pound-force according to the Anglo-American + unit system divided by the power of the unit inch according to the Anglo-American + and Imperial system of units with the exponent 2) divided by the unit inch according + to the Anglo-American and Imperial system of units . + conversion: + factor: 271447.1 + base_units: [] +- Status: '' + LevelAndCategory: 1M + Name: cubic metre per second square metre + ConversionFactor: m/s + Symbol: "(m³/s)/m²" + CommonCode: P87 + Description: Unit of volume flow cubic meters by second related to the transmission + surface in square metres. + conversion: + factor: 1.0 + base_units: + - MTS + - P87 +- Status: '' + LevelAndCategory: '3.5' + Name: rhe + ConversionFactor: 10 m x kg⁻¹ x s + Symbol: rhe + CommonCode: P88 + Description: Non SI-conforming unit of fluidity of dynamic viscosity. + conversion: + factor: 10.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: pound-force foot per inch + ConversionFactor: 53,378 66 m x kg / s² + Symbol: lbf·ft/in + CommonCode: P89 + Description: Unit for length-related rotational moment according to the Anglo-American + and Imperial system of units. + conversion: + factor: 53.37866 + base_units: [] +- Status: X + LevelAndCategory: '3.2' + Name: nine pack + ConversionFactor: '' + Symbol: '' + CommonCode: P9 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: pound-force inch per inch + ConversionFactor: 4,448 222 m x kg / s² + Symbol: lbf·in/in + CommonCode: P90 + Description: Unit for length-related rotational moment according to the Anglo-American + and Imperial system of units. + conversion: + factor: 4.448222 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: perm (0 ºC) + ConversionFactor: 5,721 35 x 10⁻¹¹ kg/(m² x Pa x s) + Symbol: 'perm (0 ºC) ' + CommonCode: P91 + Description: Traditional unit for the ability of a material to allow the transition + of the steam, defined at a temperature of 0 °C as steam transmittance, where the + mass of one grain steam penetrates an area of one foot squared at a pressure from + one inch mercury per hour. + conversion: + factor: 5.72135e-11 + base_units: + - Q28 +- Status: '' + LevelAndCategory: '3.5' + Name: perm (23 ºC) + ConversionFactor: 5,745 25 x 10⁻¹¹ kg/(m² x Pa x s) + Symbol: 'perm (23 ºC) ' + CommonCode: P92 + Description: Traditional unit for the ability of a material to allow the transition + of the steam, defined at a temperature of 23 °C as steam transmittance at which + the mass of one grain of steam penetrates an area of one square foot at a pressure + of one inch mercury per hour. + conversion: + factor: 5.74525e-11 + base_units: + - Q28 +- Status: '' + LevelAndCategory: '3.6' + Name: byte per second + ConversionFactor: byte/s + Symbol: byte/s + CommonCode: P93 + Description: Unit byte divided by the SI base unit second. + conversion: + factor: 1.0 + base_units: + - P93 +- Status: '' + LevelAndCategory: '3.6' + Name: kilobyte per second + ConversionFactor: 10³ byte/s + Symbol: kbyte/s + CommonCode: P94 + Description: 1000-fold of the unit byte divided by the SI base unit second. + conversion: + factor: 1000.0 + base_units: + - P93 +- Status: '' + LevelAndCategory: '3.6' + Name: megabyte per second + ConversionFactor: 10⁶ byte/s + Symbol: Mbyte/s + CommonCode: P95 + Description: 1 000 000-fold of the unit byte divided by the SI base unit second. + conversion: + factor: 1000000.0 + base_units: + - P93 +- Status: '' + LevelAndCategory: '3.5' + Name: reciprocal volt + ConversionFactor: m⁻² x kg⁻¹ x s³ x A + Symbol: 1/V + CommonCode: P96 + Description: Reciprocal of the derived SI unit volt. + conversion: + factor: 1.0 + base_units: + - P96 +- Status: '' + LevelAndCategory: '3.5' + Name: reciprocal radian + ConversionFactor: 1/rad + Symbol: 1/rad + CommonCode: P97 + Description: Reciprocal of the unit radian. + conversion: + factor: 1.0 + base_units: + - P97 +- Status: '' + LevelAndCategory: '3.5' + Name: pascal to the power sum of stoichiometric numbers + ConversionFactor: '' + Symbol: PaΣνB + CommonCode: P98 + Description: Unit of the equilibrium constant on the basis of the pressure(ISO 80000-9:2009, + 9-35.a). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: mole per cubiv metre to the power sum of stoichiometric numbers + ConversionFactor: '' + Symbol: "(mol/m³)∑νB" + CommonCode: P99 + Description: Unit of the equilibrium constant on the basis of the concentration + (ISO 80000-9:2009, 9-36.a). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: packet + ConversionFactor: '' + Symbol: '' + CommonCode: PA + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: pascal + ConversionFactor: Pa + Symbol: Pa + CommonCode: PAL + Description: '' + conversion: + factor: 1.0 + base_units: + - C55 + - PAL +- Status: X + LevelAndCategory: '3.8' + Name: pair inch + ConversionFactor: '' + Symbol: '' + CommonCode: PB + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: pad + ConversionFactor: '' + Symbol: '' + CommonCode: PD + Description: 'A unit of count defining the number of pads (pad: block of paper sheets + fastened together at one end).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: pound equivalent + ConversionFactor: '' + Symbol: '' + CommonCode: PE + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: pallet (lift) + ConversionFactor: '' + Symbol: '' + CommonCode: PF + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: proof litre + ConversionFactor: '' + Symbol: '' + CommonCode: PFL + Description: A unit of volume equal to one litre of proof spirits, or the alcohol + equivalent thereof. Used for measuring the strength of distilled alcoholic liquors, + expressed as a percentage of the alcohol content of a standard mixture at a specific + temperature. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: plate + ConversionFactor: '' + Symbol: '' + CommonCode: PG + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: proof gallon + ConversionFactor: '' + Symbol: '' + CommonCode: PGL + Description: A unit of volume equal to one gallon of proof spirits, or the alcohol + equivalent thereof. Used for measuring the strength of distilled alcoholic liquors, + expressed as a percentage of the alcohol content of a standard mixture at a specific + temperature. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: pitch + ConversionFactor: '' + Symbol: '' + CommonCode: PI + Description: A unit of count defining the number of characters that fit in a horizontal + inch. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: pack + ConversionFactor: '' + Symbol: '' + CommonCode: PK + Description: 'Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet).,Synonym: package' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: pail + ConversionFactor: '' + Symbol: '' + CommonCode: PL + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: degree Plato + ConversionFactor: '' + Symbol: "°P" + CommonCode: PLA + Description: A unit of proportion defining the sugar content of a product, especially + in relation to beer. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: pound percentage + ConversionFactor: '' + Symbol: '' + CommonCode: PM + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: pound net + ConversionFactor: '' + Symbol: '' + CommonCode: PN + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: pound per inch of length + ConversionFactor: 1,785 797 x 10¹ kg/m + Symbol: lb/in + CommonCode: PO + Description: '' + conversion: + factor: 17.85797 + base_units: + - KL +- Status: '' + LevelAndCategory: '3.5' + Name: page per inch + ConversionFactor: '' + Symbol: ppi + CommonCode: PQ + Description: A unit of quantity defining the degree of thickness of a bound publication, + expressed as the number of pages per inch of thickness. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: pair + ConversionFactor: '2' + Symbol: '' + CommonCode: PR + Description: 'A unit of count defining the number of pairs (pair: item described + by two''s).' + conversion: + factor: 2.0 +- Status: '' + LevelAndCategory: '2' + Name: pound-force per square inch + ConversionFactor: 6,894 757 x 10³ Pa + Symbol: lbf/in² + CommonCode: PS + Description: '' + conversion: + factor: 6894.757 + base_units: + - C55 + - PAL +- Status: D + LevelAndCategory: '2' + Name: pint (US) + ConversionFactor: 4, 731 76 x 10⁻⁴ m³ + Symbol: pt (US) + CommonCode: PT + Description: Use liquid pint (common code PTL) + conversion: + factor: 0.000473176 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: dry pint (US) + ConversionFactor: 5,506 105 x 10⁻⁴ m³ + Symbol: dry pt (US) + CommonCode: PTD + Description: '' + conversion: + factor: 0.0005506105 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: pint (UK) + ConversionFactor: 5, 682 61 x 10⁻⁴ m³ + Symbol: pt (UK) + CommonCode: PTI + Description: '' + conversion: + factor: 0.000568261 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: liquid pint (US) + ConversionFactor: 4, 731 765 x 10⁻⁴ m³ + Symbol: liq pt (US) + CommonCode: PTL + Description: '' + conversion: + factor: 0.0004731765 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.5' + Name: portion + ConversionFactor: '' + Symbol: '' + CommonCode: PTN + Description: A quantity of allowance of food allotted to, or enough for, one person. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: tray / tray pack + ConversionFactor: '' + Symbol: '' + CommonCode: PU + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: half pint (US) + ConversionFactor: '' + Symbol: '' + CommonCode: PV + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: pound per inch of width + ConversionFactor: '' + Symbol: '' + CommonCode: PW + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: peck dry (US) + ConversionFactor: '' + Symbol: '' + CommonCode: PY + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: peck dry (UK) + ConversionFactor: '' + Symbol: '' + CommonCode: PZ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: joule per tesla + ConversionFactor: m² x A + Symbol: J/T + CommonCode: Q10 + Description: Unit of the magnetic dipole moment of the molecule as derived SI unit + joule divided by the derived SI unit tesla. + conversion: + factor: 1.0 + base_units: + - Q10 +- Status: '' + LevelAndCategory: '3.6' + Name: erlang + ConversionFactor: 1 E + Symbol: E + CommonCode: Q11 + Description: Unit of the market value according to the feature of a single feature + as a statistical measurement of the existing utilization. + conversion: + factor: 1.0 + base_units: + - Q11 +- Status: '' + LevelAndCategory: '3.6' + Name: octet + ConversionFactor: 8 bit + Symbol: o + CommonCode: Q12 + Description: 'Synonym for byte: 1 octet = 8 bit = 1 byte.' + conversion: + factor: 8.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.6' + Name: octet per second + ConversionFactor: 8 bit/s + Symbol: o/s + CommonCode: Q13 + Description: Unit octet divided by the SI base unit second. + conversion: + factor: 8.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.6' + Name: shannon + ConversionFactor: '' + Symbol: Sh + CommonCode: Q14 + Description: Logarithmic unit for information equal to the content of decision of + a sentence of two mutually exclusive events, expressed as a logarithm to base + 2. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: hartley + ConversionFactor: '' + Symbol: Hart + CommonCode: Q15 + Description: Logarithmic unit for information equal to the content of decision of + a sentence of ten mutually exclusive events, expressed as a logarithm to base + 10. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.6' + Name: natural unit of information + ConversionFactor: nat + Symbol: nat + CommonCode: Q16 + Description: Logarithmic unit for information equal to the content of decision of + a sentence of ,718 281 828 459 mutually exclusive events, expressed as a logarithm + to base Euler value e. + conversion: + factor: 1.0 + base_units: + - Q16 +- Status: '' + LevelAndCategory: '3.6' + Name: shannon per second + ConversionFactor: Sh/s + Symbol: Sh/s + CommonCode: Q17 + Description: Time related logarithmic unit for information equal to the content + of decision of a sentence of two mutually exclusive events, expressed as a logarithm + to base 2. + conversion: + factor: 1.0 + base_units: + - Q17 +- Status: '' + LevelAndCategory: '3.6' + Name: hartley per second + ConversionFactor: Hart/s + Symbol: Hart/s + CommonCode: Q18 + Description: Time related logarithmic unit for information equal to the content + of decision of a sentence of ten mutually exclusive events, expressed as a logarithm + to base 10. + conversion: + factor: 1.0 + base_units: + - Q18 +- Status: '' + LevelAndCategory: '3.6' + Name: natural unit of information per second + ConversionFactor: nat/s + Symbol: nat/s + CommonCode: Q19 + Description: Time related logarithmic unit for information equal to the content + of decision of a sentence of 2,718 281 828 459 mutually exclusive events, expressed + as a logarithm to base of the Euler value e. + conversion: + factor: 1.0 + base_units: + - Q19 +- Status: '' + LevelAndCategory: '3.5' + Name: second per kilogramm + ConversionFactor: kg⁻¹ x s + Symbol: s/kg + CommonCode: Q20 + Description: Unit of the Einstein transition probability for spontaneous or inducing + emissions and absorption according to ISO 80000-7:2008, expressed as SI base unit + second divided by the SI base unit kilogram. + conversion: + factor: 1.0 + base_units: + - Q20 +- Status: '' + LevelAndCategory: '3.5' + Name: watt square metre + ConversionFactor: m⁴ x kg x s⁻³ + Symbol: W·m² + CommonCode: Q21 + Description: Unit of the first radiation constants c1 = 2·p·h·c0², the value of + which is 3,741 771 18·10?¹6-fold that of the comparative value of the product + of the derived SI unit watt multiplied with the power of the SI base unit metre + with the exponent 2. + conversion: + factor: 1.0 + base_units: + - Q21 +- Status: '' + LevelAndCategory: '3.5' + Name: second per radian cubic metre + ConversionFactor: m⁻³ x s x rad⁻¹ + Symbol: 1/(Hz·rad·m³) + CommonCode: Q22 + Description: Unit of the density of states as an expression of angular frequency + as complement of the product of hertz and radiant and the power of SI base unit + metre by exponent 3 . + conversion: + factor: 1.0 + base_units: + - Q22 +- Status: '' + LevelAndCategory: '3.5' + Name: weber to the power minus one + ConversionFactor: m⁻² x kg⁻¹ x s² x A + Symbol: 1/Wb + CommonCode: Q23 + Description: Complement of the derived SI unit weber as unit of the Josephson constant, + which value is equal to the 384 597,891-fold of the reference value gigahertz + divided by volt. + conversion: + factor: 1.0 + base_units: + - Q23 +- Status: '' + LevelAndCategory: '3.5' + Name: reciprocal inch + ConversionFactor: 39,370 08 m⁻¹ + Symbol: 1/in + CommonCode: Q24 + Description: Complement of the unit inch according to the Anglo-American and Imperial + system of units. + conversion: + factor: 39.37008 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '3.5' + Name: dioptre + ConversionFactor: m⁻¹ + Symbol: dpt + CommonCode: Q25 + Description: 'Unit used at the statement of relative refractive indexes of optical + systems as complement of the focal length with correspondence to: 1 dpt = 1/m.' + conversion: + factor: 1.0 + base_units: + - C92 + - Q25 +- Status: '' + LevelAndCategory: '3.5' + Name: one per one + ConversionFactor: 1/1 + Symbol: 1/1 + CommonCode: Q26 + Description: Value of the quotient from two physical units of the same kind as a + numerator and denominator whereas the units are shortened mutually. + conversion: + factor: 1.0 + base_units: + - Q26 +- Status: '' + LevelAndCategory: '3.5' + Name: newton metre per metre + ConversionFactor: m x kg x s⁻² + Symbol: N·m/m² + CommonCode: Q27 + Description: Unit for length-related rotational moment as product of the derived + SI unit newton and the SI base unit metre divided by the SI base unit metre. + conversion: + factor: 1.0 + base_units: + - Q27 +- Status: '' + LevelAndCategory: '3.5' + Name: kilogram per square metre pascal second + ConversionFactor: kg/(m² x Pa x s) + Symbol: kg/(m²·Pa·s) + CommonCode: Q28 + Description: Unit for the ability of a material to allow the transition of steam. + conversion: + factor: 1.0 + base_units: + - Q28 +- Status: '' + LevelAndCategory: 1S + Name: microgram per hectogram + ConversionFactor: 10⁻8 + Symbol: µg/hg + CommonCode: Q29 + Description: Microgram per hectogram. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: pH (potential of Hydrogen) + ConversionFactor: "-log10(mol/l)" + Symbol: pH + CommonCode: Q30 + Description: The activity of the (solvated) hydrogen ion (a logarithmic measure + used to state the acidity or alkalinity of a chemical solution). + conversion: + factor: 1.0 + base_units: + - Q30 +- Status: '' + LevelAndCategory: 1S + Name: kilojoule per gram + ConversionFactor: 10⁶ J/kg + Symbol: kJ/g + CommonCode: Q31 + Description: '' + conversion: + factor: 1000000.0 + base_units: + - J2 +- Status: '' + LevelAndCategory: 1S + Name: femtolitre + ConversionFactor: 10-18 m3 + Symbol: fl + CommonCode: Q32 + Description: '' + conversion: + factor: 1.0e-18 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: picolitre + ConversionFactor: 10-15 m3 + Symbol: pl + CommonCode: Q33 + Description: '' + conversion: + factor: 1.0e-15 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1S + Name: nanolitre + ConversionFactor: 10-12 m3 + Symbol: nl + CommonCode: Q34 + Description: '' + conversion: + factor: 1.0e-12 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: 1M + Name: megawatts per minute + ConversionFactor: 1.667 × 104 W/s + Symbol: MW/min + CommonCode: Q35 + Description: A unit of power defining the total amount of bulk energy transferred + or consumer per minute. + conversion: + factor: 1.0 + base_units: + - Q35 +- Status: '' + LevelAndCategory: '3.1' + Name: square metre per cubic metre + ConversionFactor: 1 m2/m3 + Symbol: m2/m3 + CommonCode: Q36 + Description: A unit of the amount of surface area per unit volume of an object or + collection of objects. + conversion: + factor: 1.0 + base_units: + - Q36 +- Status: "¦" + LevelAndCategory: '2.0' + Name: Standard cubic metre per day + ConversionFactor: 1.15741 × 10-5 m3/s + Symbol: '' + CommonCode: Q37 + Description: Standard cubic metre (temperature 15°C and pressure 1013.25 millibars + ) per day + conversion: + factor: 1.0 + base_units: + - Q37 + - Q39 +- Status: "¦" + LevelAndCategory: '2.0' + Name: Standard cubic metre per hour + ConversionFactor: 2.77778 × 10-4 m3/s + Symbol: '' + CommonCode: Q38 + Description: Standard cubic metre (temperature 15°C and pressure 1013.25 millibars + ) per hour + conversion: + factor: 2.0 + base_units: [] +- Status: "¦" + LevelAndCategory: '2.0' + Name: Normalized cubic metre per day + ConversionFactor: 1.15741 × 10-5 m3/s + Symbol: '' + CommonCode: Q39 + Description: Normalized cubic metre (temperature 0°C and pressure 1013.25 millibars + ) per day + conversion: + factor: 1.0 + base_units: + - Q37 + - Q39 +- Status: "¦" + LevelAndCategory: '2.0' + Name: Normalized cubic metre per hour + ConversionFactor: 2.77778 × 10-4 m3/s + Symbol: '' + CommonCode: Q40 + Description: Normalized cubic metre (temperature 0°C and pressure 1013.25 millibars + ) per hour + conversion: + factor: 2.0 + base_units: [] +- Status: "¦" + LevelAndCategory: '2.0' + Name: Joule per normalised cubic metre + ConversionFactor: '' + Symbol: '' + CommonCode: Q41 + Description: Joule per normalised cubic metre (temperature 0°C and pressure 1013.25 + millibars). + conversion: + factor: 1.0 +- Status: "¦" + LevelAndCategory: '2.0' + Name: Joule per standard cubic metre + ConversionFactor: '' + Symbol: '' + CommonCode: Q42 + Description: Joule per standard cubic metre (temperature 15°C and pressure 1013.25 + millibars). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: meal + ConversionFactor: '' + Symbol: '' + CommonCode: Q3 + Description: 'A unit of count defining the number of meals (meal: an amount of food + to be eaten on a single occasion).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: page - facsimile + ConversionFactor: '' + Symbol: '' + CommonCode: QA + Description: A unit of count defining the number of facsimile pages. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: quarter (of a year) + ConversionFactor: '' + Symbol: '' + CommonCode: QAN + Description: A unit of time defining the number of quarters (3 months). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: page - hardcopy + ConversionFactor: '' + Symbol: '' + CommonCode: QB + Description: 'A unit of count defining the number of hardcopy pages (hardcopy page: + a page rendered as printed or written output on paper, film, or other permanent + medium).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.7' + Name: quarter dozen + ConversionFactor: '3' + Symbol: '' + CommonCode: QD + Description: '' + conversion: + factor: 3.0 +- Status: X + LevelAndCategory: '3.8' + Name: quarter hour + ConversionFactor: 900 s + Symbol: '' + CommonCode: QH + Description: '' + conversion: + factor: 900.0 + base_units: + - H04 + - SEC +- Status: X + LevelAndCategory: '3.8' + Name: quarter kilogram + ConversionFactor: '' + Symbol: '' + CommonCode: QK + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: quire + ConversionFactor: '' + Symbol: qr + CommonCode: QR + Description: 'A unit of count for paper, expressed as the number of quires (quire: + a number of paper sheets, typically 25).' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: quart (US) + ConversionFactor: 0,946 352 9 x 10⁻³ m³ + Symbol: qt (US) + CommonCode: QT + Description: Use liquid quart (common code QTL) + conversion: + factor: 0.0009463529 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: dry quart (US) + ConversionFactor: 1,101 221 x 10⁻³ m³ + Symbol: dry qt (US) + CommonCode: QTD + Description: '' + conversion: + factor: 0.001101221 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: quart (UK) + ConversionFactor: 1,136 522 5 x 10⁻³ m³ + Symbol: qt (UK) + CommonCode: QTI + Description: '' + conversion: + factor: 0.0011365225 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: liquid quart (US) + ConversionFactor: 9,463 529 x 10⁻⁴ m³ + Symbol: liq qt (US) + CommonCode: QTL + Description: '' + conversion: + factor: 0.0009463529 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.5' + Name: quarter (UK) + ConversionFactor: 12,700 59 kg + Symbol: Qr (UK) + CommonCode: QTR + Description: A traditional unit of weight equal to 1/4 hundredweight. In the United + Kingdom, one quarter equals 28 pounds. + conversion: + factor: 12.7 + base_units: [] +- Status: '' + LevelAndCategory: '3.5' + Name: pica + ConversionFactor: 4,217 518 x 10⁻³ m + Symbol: '' + CommonCode: R1 + Description: 'A unit of count defining the number of picas. (pica: typographical + length equal to 12 points or 4.22 mm (approx.)).' + conversion: + factor: 0.004217518 + base_units: + - MTR +- Status: X + LevelAndCategory: '3.5' + Name: calorie + ConversionFactor: 4,186 8 J + Symbol: cal + CommonCode: R4 + Description: Use International Table (IT) calorie (common code D70) + conversion: + factor: 4.1868 + base_units: + - JOU +- Status: '' + LevelAndCategory: '3.8' + Name: thousand cubic metre + ConversionFactor: 10³m³ + Symbol: '' + CommonCode: R9 + Description: A unit of volume equal to one thousand cubic metres. + conversion: + factor: 1000.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.3' + Name: rack + ConversionFactor: '' + Symbol: '' + CommonCode: RA + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: rod + ConversionFactor: '' + Symbol: '' + CommonCode: RD + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: ring + ConversionFactor: '' + Symbol: '' + CommonCode: RG + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: running or operating hour + ConversionFactor: '' + Symbol: '' + CommonCode: RH + Description: A unit of time defining the number of hours of operation. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: roll metric measure + ConversionFactor: '' + Symbol: '' + CommonCode: RK + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: reel + ConversionFactor: '' + Symbol: '' + CommonCode: RL + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: ream + ConversionFactor: '' + Symbol: '' + CommonCode: RM + Description: 'A unit of count for paper, expressed as the number of reams (ream: + a large quantity of paper sheets, typically 500).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: ream metric measure + ConversionFactor: '' + Symbol: '' + CommonCode: RN + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: roll + ConversionFactor: '' + Symbol: '' + CommonCode: RO + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: room + ConversionFactor: '' + Symbol: '' + CommonCode: ROM + Description: A unit of count defining the number of rooms. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: pound per ream + ConversionFactor: '' + Symbol: '' + CommonCode: RP + Description: 'A unit of mass for paper, expressed as pounds per ream. (ream: a large + quantity of paper, typically 500 sheets).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: revolutions per minute + ConversionFactor: 1,67 x 10⁻²/s + Symbol: r/min + CommonCode: RPM + Description: Refer ISO/TC12 SI Guide + conversion: + factor: 0.0167 + base_units: + - RPS +- Status: '' + LevelAndCategory: '1' + Name: revolutions per second + ConversionFactor: 1/s + Symbol: r/s + CommonCode: RPS + Description: Refer ISO/TC12 SI Guide + conversion: + factor: 1.0 + base_units: + - RPS +- Status: X + LevelAndCategory: '3.9' + Name: reset + ConversionFactor: '' + Symbol: '' + CommonCode: RS + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: revenue ton mile + ConversionFactor: '' + Symbol: '' + CommonCode: RT + Description: 'A unit of information typically used for billing purposes, expressed + as the number of revenue tons (revenue ton: either a metric ton or a cubic metres, + whichever is the larger), moved over a distance of one mile.' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: run + ConversionFactor: '' + Symbol: '' + CommonCode: RU + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: square foot per second + ConversionFactor: 0,092 903 04 m²/s + Symbol: ft²/s + CommonCode: S3 + Description: 'Synonym: foot squared per second' + conversion: + factor: 0.09290304 + base_units: + - S4 +- Status: '' + LevelAndCategory: '1' + Name: square metre per second + ConversionFactor: m²/s + Symbol: m²/s + CommonCode: S4 + Description: 'Synonym: metre squared per second (square metres/second US)' + conversion: + factor: 1.0 + base_units: + - S4 +- Status: X + LevelAndCategory: '3.8' + Name: sixty fourths of an inch + ConversionFactor: '' + Symbol: '' + CommonCode: S5 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: session + ConversionFactor: '' + Symbol: '' + CommonCode: S6 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: storage unit + ConversionFactor: '' + Symbol: '' + CommonCode: S7 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: standard advertising unit + ConversionFactor: '' + Symbol: '' + CommonCode: S8 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: sack + ConversionFactor: '' + Symbol: '' + CommonCode: SA + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: half year (6 months) + ConversionFactor: '' + Symbol: '' + CommonCode: SAN + Description: A unit of time defining the number of half years (6 months). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: score + ConversionFactor: '20' + Symbol: '' + CommonCode: SCO + Description: A unit of count defining the number of units in multiples of 20. + conversion: + factor: 20.0 +- Status: '' + LevelAndCategory: '3.5' + Name: scruple + ConversionFactor: 1,295 982 g + Symbol: '' + CommonCode: SCR + Description: '' + conversion: + factor: 1.295982 + base_units: [] +- Status: X + LevelAndCategory: '3.1' + Name: solid pound + ConversionFactor: '' + Symbol: '' + CommonCode: SD + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: section + ConversionFactor: '' + Symbol: '' + CommonCode: SE + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: second [unit of time] + ConversionFactor: s + Symbol: s + CommonCode: SEC + Description: '' + conversion: + factor: 1.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.2' + Name: set + ConversionFactor: '' + Symbol: '' + CommonCode: SET + Description: 'A unit of count defining the number of sets (set: a number of objects + grouped together).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: segment + ConversionFactor: '' + Symbol: '' + CommonCode: SG + Description: A unit of information equal to 64000 bytes. + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '3.4' + Name: shipping ton + ConversionFactor: '' + Symbol: '' + CommonCode: SHT + Description: A unit of mass defining the number of tons for shipping. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: siemens + ConversionFactor: A/V + Symbol: S + CommonCode: SIE + Description: '' + conversion: + factor: 1.0 + base_units: + - SIE +- Status: X + LevelAndCategory: '3.4' + Name: split tank truck + ConversionFactor: '' + Symbol: '' + CommonCode: SK + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: slipsheet + ConversionFactor: '' + Symbol: '' + CommonCode: SL + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: "¦" + LevelAndCategory: '2' + Name: Standard cubic metre + ConversionFactor: m3 + Symbol: '' + CommonCode: SM3 + Description: Standard cubic metre (temperature 15°C and pressure 1013.25 millibars + ) + conversion: + factor: 1.0 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '2' + Name: mile (statute mile) + ConversionFactor: 1 609,344 m + Symbol: mile + CommonCode: SMI + Description: '' + conversion: + factor: 1609.344 + base_units: + - MTR +- Status: X + LevelAndCategory: '3.8' + Name: square rod + ConversionFactor: 25,292 9 m² + Symbol: rd² + CommonCode: SN + Description: '' + conversion: + factor: 25.2929 + base_units: + - MTK +- Status: X + LevelAndCategory: '3.3' + Name: spool + ConversionFactor: '' + Symbol: '' + CommonCode: SO + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: shelf package + ConversionFactor: '' + Symbol: '' + CommonCode: SP + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: square + ConversionFactor: '' + Symbol: '' + CommonCode: SQ + Description: 'A unit of count defining the number of squares (square: rectangular + shape).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: square, roofing + ConversionFactor: '' + Symbol: '' + CommonCode: SQR + Description: A unit of count defining the number of squares of roofing materials, + measured in multiples of 100 square feet. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: strip + ConversionFactor: '' + Symbol: '' + CommonCode: SR + Description: 'A unit of count defining the number of strips (strip: long narrow + piece of an object).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: sheet metric measure + ConversionFactor: '' + Symbol: '' + CommonCode: SS + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: short standard (7200 matches) + ConversionFactor: '' + Symbol: '' + CommonCode: SST + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: sheet + ConversionFactor: '' + Symbol: '' + CommonCode: ST + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: stick + ConversionFactor: '' + Symbol: '' + CommonCode: STC + Description: 'A unit of count defining the number of sticks (stick: slender and + often cylindrical piece of a substance).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: stone (UK) + ConversionFactor: 6,350 293 kg + Symbol: st + CommonCode: STI + Description: '' + conversion: + factor: 6.350293 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.9' + Name: stick, cigarette + ConversionFactor: '' + Symbol: '' + CommonCode: STK + Description: A unit of count defining the number of cigarettes in the smallest unit + for stock-taking and/or duty computation. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: standard litre + ConversionFactor: '' + Symbol: '' + CommonCode: STL + Description: A unit of volume defining the number of litres of a product at a temperature + of 15 degrees Celsius, especially in relation to hydrocarbon oils. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: ton (US) or short ton (UK/US) + ConversionFactor: 0,907184 7 x 10³ kg + Symbol: ton (US) + CommonCode: STN + Description: 'Synonym: net ton (2000 lb)' + conversion: + factor: 907.1847 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.9' + Name: straw + ConversionFactor: '' + Symbol: '' + CommonCode: STW + Description: 'A unit of count defining the number of straws (straw: a slender tube + used for sucking up liquids).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: skid + ConversionFactor: '' + Symbol: '' + CommonCode: SV + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: skein + ConversionFactor: '' + Symbol: '' + CommonCode: SW + Description: 'A unit of count defining the number of skeins (skein: a loosely-coiled + bundle of yarn or thread).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: shipment + ConversionFactor: '' + Symbol: '' + CommonCode: SX + Description: 'A unit of count defining the number of shipments (shipment: an amount + of goods shipped or transported).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: syringe + ConversionFactor: '' + Symbol: '' + CommonCode: SYR + Description: 'A unit of count defining the number of syringes (syringe: a small + device for pumping, spraying and/or injecting liquids through a small aperture).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: telecommunication line in service + ConversionFactor: '' + Symbol: '' + CommonCode: T0 + Description: A unit of count defining the number of lines in service. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand pound gross + ConversionFactor: '' + Symbol: '' + CommonCode: T1 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: thousand piece + ConversionFactor: '' + Symbol: '' + CommonCode: T3 + Description: 'A unit of count defining the number of pieces in multiples of 1000 + (piece: a single item, article or exemplar).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand bag + ConversionFactor: '' + Symbol: '' + CommonCode: T4 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand casing + ConversionFactor: '' + Symbol: '' + CommonCode: T5 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand gallon (US) + ConversionFactor: 3,785 412 m³ + Symbol: '' + CommonCode: T6 + Description: '' + conversion: + factor: 3.785412 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.8' + Name: thousand impression + ConversionFactor: '' + Symbol: '' + CommonCode: T7 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand linear inch + ConversionFactor: '' + Symbol: '' + CommonCode: T8 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: tenth cubic foot + ConversionFactor: '' + Symbol: '' + CommonCode: TA + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1M + Name: kiloampere hour (thousand ampere hour) + ConversionFactor: 3,6 x 10⁶ C + Symbol: kA·h + CommonCode: TAH + Description: '' + conversion: + factor: 3600000.0 + base_units: + - A8 +- Status: '' + LevelAndCategory: '3.5' + Name: total acid number + ConversionFactor: 'mg KOH/g ' + Symbol: '' + CommonCode: TAN + Description: A unit of chemistry defining the amount of potassium hydroxide (KOH) + in milligrams that is needed to neutralize the acids in one gram of oil. It is + an important quality measurement of crude oil. + conversion: + factor: 1.0 + base_units: + - TAN +- Status: X + LevelAndCategory: '3.4' + Name: truckload + ConversionFactor: '' + Symbol: '' + CommonCode: TC + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: therm + ConversionFactor: 10⁵ x 1 055,056 J + Symbol: '' + CommonCode: TD + Description: '' + conversion: + factor: 100000.0 + base_units: [] +- Status: X + LevelAndCategory: '3.3' + Name: tote + ConversionFactor: '' + Symbol: '' + CommonCode: TE + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: ten square yard + ConversionFactor: '' + Symbol: '' + CommonCode: TF + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: thousand square inch + ConversionFactor: '' + Symbol: '' + CommonCode: TI + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: metric ton, including container + ConversionFactor: '' + Symbol: '' + CommonCode: TIC + Description: A unit of mass defining the number of metric tons of a product, including + its container. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: metric ton, including inner packaging + ConversionFactor: '' + Symbol: '' + CommonCode: TIP + Description: A unit of mass defining the number of metric tons of a product, including + its inner packaging materials. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand square centimetre + ConversionFactor: '' + Symbol: '' + CommonCode: TJ + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: tank, rectangular + ConversionFactor: '' + Symbol: '' + CommonCode: TK + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.4' + Name: tonne kilometre + ConversionFactor: 10⁶ kg x m + Symbol: t·km + CommonCode: TKM + Description: 'A unit of information typically used for billing purposes, expressed + as the number of tonnes (metric tons) moved over a distance of one kilometre. ' + conversion: + factor: 1000000.0 + base_units: + - M94 +- Status: X + LevelAndCategory: '3.8' + Name: thousand foot (linear) + ConversionFactor: '' + Symbol: '' + CommonCode: TL + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: kilogram of imported meat, less offal + ConversionFactor: '' + Symbol: '' + CommonCode: TMS + Description: A unit of mass equal to one thousand grams of imported meat, disregarding + less valuable by-products such as the entrails. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: tin + ConversionFactor: '' + Symbol: '' + CommonCode: TN + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: 1S + Name: tonne (metric ton) + ConversionFactor: 10³ kg + Symbol: t + CommonCode: TNE + Description: 'Synonym: metric ton' + conversion: + factor: 1000.0 + base_units: + - KGM +- Status: '' + LevelAndCategory: '3.2' + Name: ten pack + ConversionFactor: '' + Symbol: '' + CommonCode: TP + Description: A unit of count defining the number of items in multiples of 10. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: teeth per inch + ConversionFactor: 0.0254 /m + Symbol: '' + CommonCode: TPI + Description: The number of teeth per inch. + conversion: + factor: 0.0 + base_units: [] +- Status: '' + LevelAndCategory: '3.8' + Name: ten pair + ConversionFactor: '' + Symbol: '' + CommonCode: TPR + Description: 'A unit of count defining the number of pairs in multiples of 10 (pair: + item described by two''s).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand foot + ConversionFactor: '' + Symbol: '' + CommonCode: TQ + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.8' + Name: thousand cubic metre per day + ConversionFactor: 1,157 41 x 10⁻² m³/s + Symbol: km³/d + CommonCode: TQD + Description: A unit of volume equal to one thousand cubic metres per day. + conversion: + factor: 0.0115741 + base_units: + - MQS +- Status: X + LevelAndCategory: '3.8' + Name: ten square foot + ConversionFactor: '' + Symbol: '' + CommonCode: TR + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.7' + Name: trillion (EUR) + ConversionFactor: 10¹⁸ + Symbol: '' + CommonCode: TRL + Description: '' + conversion: + factor: 1.0e+18 +- Status: X + LevelAndCategory: '3.8' + Name: thousand square foot + ConversionFactor: '' + Symbol: '' + CommonCode: TS + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: tonne of substance 90 % dry + ConversionFactor: '' + Symbol: '' + CommonCode: TSD + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.1' + Name: ton of steam per hour + ConversionFactor: '' + Symbol: '' + CommonCode: TSH + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: ten set + ConversionFactor: '' + Symbol: '' + CommonCode: TST + Description: 'A unit of count defining the number of sets in multiples of 10 (set: + a number of objects grouped together).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand linear metre + ConversionFactor: '' + Symbol: '' + CommonCode: TT + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: ten thousand sticks + ConversionFactor: '' + Symbol: '' + CommonCode: TTS + Description: 'A unit of count defining the number of sticks in multiples of 10000 + (stick: slender and often cylindrical piece of a substance).' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: tube + ConversionFactor: '' + Symbol: '' + CommonCode: TU + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: thousand kilogram + ConversionFactor: 10³kg + Symbol: '' + CommonCode: TV + Description: '' + conversion: + factor: 1000.0 + base_units: + - KGM +- Status: X + LevelAndCategory: '3.8' + Name: thousand sheet + ConversionFactor: '' + Symbol: '' + CommonCode: TW + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: tank, cylindrical + ConversionFactor: '' + Symbol: '' + CommonCode: TY + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: treatment + ConversionFactor: '' + Symbol: '' + CommonCode: U1 + Description: 'A unit of count defining the number of treatments (treatment: subjection + to the action of a chemical, physical or biological agent).' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: tablet + ConversionFactor: '' + Symbol: '' + CommonCode: U2 + Description: 'A unit of count defining the number of tablets (tablet: a small flat + or compressed solid object).' + conversion: + factor: 1.0 +- Status: D + LevelAndCategory: '2' + Name: torr + ConversionFactor: 133,322 4 Pa + Symbol: Torr + CommonCode: UA + Description: '' + conversion: + factor: 133.3224 + base_units: + - C55 + - PAL +- Status: '' + LevelAndCategory: '3.5' + Name: telecommunication line in service average + ConversionFactor: '' + Symbol: '' + CommonCode: UB + Description: A unit of count defining the average number of lines in service. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: telecommunication port + ConversionFactor: '' + Symbol: '' + CommonCode: UC + Description: A unit of count defining the number of network access ports. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: tenth minute + ConversionFactor: 6 s + Symbol: '' + CommonCode: UD + Description: '' + conversion: + factor: 6.0 + base_units: + - H04 + - SEC +- Status: X + LevelAndCategory: '3.8' + Name: tenth hour + ConversionFactor: 360 s + Symbol: '' + CommonCode: UE + Description: '' + conversion: + factor: 360.0 + base_units: + - H04 + - SEC +- Status: X + LevelAndCategory: '3.5' + Name: usage per telecommunication line average + ConversionFactor: '' + Symbol: '' + CommonCode: UF + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: ten thousand yard + ConversionFactor: '' + Symbol: '' + CommonCode: UH + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: million unit + ConversionFactor: '' + Symbol: '' + CommonCode: UM + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: volt - ampere per kilogram + ConversionFactor: V x A / kg + Symbol: V·A / kg + CommonCode: VA + Description: '' + conversion: + factor: 1.0 + base_units: + - VA +- Status: X + LevelAndCategory: '3.3' + Name: vial + ConversionFactor: '' + Symbol: '' + CommonCode: VI + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: volt + ConversionFactor: V + Symbol: V + CommonCode: VLT + Description: '' + conversion: + factor: 1.0 + base_units: + - 2G + - 2H + - VLT +- Status: '' + LevelAndCategory: '3.7' + Name: percent volume + ConversionFactor: '' + Symbol: '' + CommonCode: VP + Description: A measure of concentration, typically expressed as the percentage volume + of a solute in a solution. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: bulk + ConversionFactor: '' + Symbol: '' + CommonCode: VQ + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: visit + ConversionFactor: '' + Symbol: '' + CommonCode: VS + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: wet kilo + ConversionFactor: '' + Symbol: '' + CommonCode: W2 + Description: A unit of mass defining the number of kilograms of a product, including + the water content of the product. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.8' + Name: two week + ConversionFactor: '' + Symbol: '' + CommonCode: W4 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: watt per kilogram + ConversionFactor: 1 W/kg + Symbol: W/kg + CommonCode: WA + Description: '' + conversion: + factor: 1.0 + base_units: + - WA +- Status: '' + LevelAndCategory: '3.1' + Name: wet pound + ConversionFactor: '' + Symbol: '' + CommonCode: WB + Description: A unit of mass defining the number of pounds of a material, including + the water content of the material. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: cord + ConversionFactor: 3,63 m³ + Symbol: '' + CommonCode: WCD + Description: A unit of volume used for measuring lumber. One board foot equals 1/12 + of a cubic foot. + conversion: + factor: 3.63 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '3.1' + Name: wet ton + ConversionFactor: '' + Symbol: '' + CommonCode: WE + Description: A unit of mass defining the number of tons of a material, including + the water content of the material. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: weber + ConversionFactor: Wb + Symbol: Wb + CommonCode: WEB + Description: '' + conversion: + factor: 1.0 + base_units: + - WEB +- Status: '' + LevelAndCategory: '2' + Name: week + ConversionFactor: 6,048 x 10⁵ s + Symbol: wk + CommonCode: WEE + Description: '' + conversion: + factor: 604800.0 + base_units: + - H04 + - SEC +- Status: '' + LevelAndCategory: '3.1' + Name: wine gallon + ConversionFactor: '' + Symbol: '' + CommonCode: WG + Description: A unit of volume equal to 231 cubic inches. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: wheel + ConversionFactor: '' + Symbol: '' + CommonCode: WH + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '1' + Name: watt hour + ConversionFactor: 3,6 x 10³ J + Symbol: W·h + CommonCode: WHR + Description: '' + conversion: + factor: 3600.0 + base_units: + - JOU +- Status: X + LevelAndCategory: '3.9' + Name: weight per square inch + ConversionFactor: '' + Symbol: '' + CommonCode: WI + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.1' + Name: working month + ConversionFactor: '' + Symbol: '' + CommonCode: WM + Description: A unit of time defining the number of working months. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: wrap + ConversionFactor: '' + Symbol: '' + CommonCode: WR + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: standard + ConversionFactor: 4,672 m³ + Symbol: std + CommonCode: WSD + Description: 'A unit of volume of finished lumber equal to 165 cubic feet.,Synonym: + standard cubic foot' + conversion: + factor: 4.672 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: '' + LevelAndCategory: '1' + Name: watt + ConversionFactor: W + Symbol: W + CommonCode: WTT + Description: '' + conversion: + factor: 1.0 + base_units: + - D46 + - P14 + - WTT +- Status: D + LevelAndCategory: '3.1' + Name: millilitre of water + ConversionFactor: '' + Symbol: '' + CommonCode: WW + Description: A unit of volume equal to the number of millilitres of water. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: Gunter's chain + ConversionFactor: 20,116 8 m + Symbol: ch (UK) + CommonCode: X1 + Description: A unit of distance used or formerly used by British surveyors. + conversion: + factor: 20.1168 + base_units: + - MTR +- Status: '' + LevelAndCategory: '2' + Name: square yard + ConversionFactor: 8,361 274 x 10⁻¹ m² + Symbol: yd² + CommonCode: YDK + Description: '' + conversion: + factor: 0.8361274 + base_units: + - MTK +- Status: '' + LevelAndCategory: '2' + Name: cubic yard + ConversionFactor: 0,764 555 m³ + Symbol: yd³ + CommonCode: YDQ + Description: '' + conversion: + factor: 0.764555 + base_units: + - D40 + - G26 + - K6 + - MTQ + - NM3 + - SM3 +- Status: X + LevelAndCategory: '3.8' + Name: hundred linear yard + ConversionFactor: '' + Symbol: '' + CommonCode: YL + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '2' + Name: yard + ConversionFactor: 0,914 4 m + Symbol: yd + CommonCode: YRD + Description: '' + conversion: + factor: 0.9144 + base_units: + - MTR +- Status: X + LevelAndCategory: '3.8' + Name: ten yard + ConversionFactor: '' + Symbol: '' + CommonCode: YT + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.4' + Name: lift van + ConversionFactor: '' + Symbol: '' + CommonCode: Z1 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: hanging container + ConversionFactor: '' + Symbol: '' + CommonCode: Z11 + Description: A unit of count defining the number of hanging containers. + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: chest + ConversionFactor: '' + Symbol: '' + CommonCode: Z2 + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: cask + ConversionFactor: '' + Symbol: '' + CommonCode: Z3 + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.3' + Name: hogshead + ConversionFactor: '' + Symbol: '' + CommonCode: Z4 + Description: Use UN/ECE Recommendation 21 (refer to Note 2 in the spreadsheet introduction, + 1st sheet). + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: lug + ConversionFactor: '' + Symbol: '' + CommonCode: Z5 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.5' + Name: conference point + ConversionFactor: '' + Symbol: '' + CommonCode: Z6 + Description: '' + conversion: + factor: 1.0 +- Status: X + LevelAndCategory: '3.9' + Name: newspage agate line + ConversionFactor: '' + Symbol: '' + CommonCode: Z8 + Description: '' + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.5' + Name: page + ConversionFactor: '' + Symbol: '' + CommonCode: ZP + Description: A unit of count defining the number of pages. + conversion: + factor: 1.0 +- Status: '' + LevelAndCategory: '3.9' + Name: mutually defined + ConversionFactor: '' + Symbol: '' + CommonCode: ZZ + Description: A unit of measure as agreed in common between two or more parties. + conversion: + factor: 1.0 diff --git a/config/units-of-measure/un-ece-20.yml b/config/units-of-measure/un-ece-20.yml new file mode 100644 index 00000000..71695a96 --- /dev/null +++ b/config/units-of-measure/un-ece-20.yml @@ -0,0 +1,15383 @@ +# downloaded json from https://github.com/datasets/unece-units-of-measure and manually converted to yaml using +# `ruby -ryaml -rjson -e 'puts YAML.dump(JSON.parse(STDIN.read))' 10 kpa +- Code: WH + Description: + Name: Intermediate bulk container, aluminium, pressurised > 10 kpa +- Code: WJ + Description: + Name: Intermediate bulk container, metal, pressure 10 kpa +- Code: WK + Description: + Name: Intermediate bulk container, steel, liquid +- Code: WL + Description: + Name: Intermediate bulk container, aluminium, liquid +- Code: WM + Description: + Name: Intermediate bulk container, metal, liquid +- Code: WN + Description: + Name: Intermediate bulk container, woven plastic, without coat/liner +- Code: WP + Description: + Name: Intermediate bulk container, woven plastic, coated +- Code: WQ + Description: + Name: Intermediate bulk container, woven plastic, with liner +- Code: WR + Description: + Name: Intermediate bulk container, woven plastic, coated and liner +- Code: WS + Description: + Name: Intermediate bulk container, plastic film +- Code: WT + Description: + Name: Intermediate bulk container, textile with out coat/liner +- Code: WU + Description: + Name: Intermediate bulk container, natural wood, with inner liner +- Code: WV + Description: + Name: Intermediate bulk container, textile, coated +- Code: WW + Description: + Name: Intermediate bulk container, textile, with liner +- Code: WX + Description: + Name: Intermediate bulk container, textile, coated and liner +- Code: WY + Description: + Name: Intermediate bulk container, plywood, with inner liner +- Code: WZ + Description: + Name: Intermediate bulk container, reconstituted wood, with inner liner +- Code: XA + Description: + Name: Bag, woven plastic, without inner coat/liner +- Code: XB + Description: + Name: Bag, woven plastic, sift proof +- Code: XC + Description: + Name: Bag, woven plastic, water resistant +- Code: XD + Description: + Name: Bag, plastics film +- Code: XF + Description: + Name: Bag, textile, without inner coat/liner +- Code: XG + Description: + Name: Bag, textile, sift proof +- Code: XH + Description: + Name: Bag, textile, water resistant +- Code: XJ + Description: + Name: Bag, paper, multi-wall +- Code: XK + Description: + Name: Bag, paper, multi-wall, water resistant +- Code: YA + Description: + Name: Composite packaging, plastic receptacle in steel drum +- Code: YB + Description: + Name: Composite packaging, plastic receptacle in steel crate box +- Code: YC + Description: + Name: Composite packaging, plastic receptacle in aluminium drum +- Code: YD + Description: + Name: Composite packaging, plastic receptacle in aluminium crate +- Code: YF + Description: + Name: Composite packaging, plastic receptacle in wooden box +- Code: YG + Description: + Name: Composite packaging, plastic receptacle in plywood drum +- Code: YH + Description: + Name: Composite packaging, plastic receptacle in plywood box +- Code: YJ + Description: + Name: Composite packaging, plastic receptacle in fibre drum +- Code: YK + Description: + Name: Composite packaging, plastic receptacle in fibreboard box +- Code: YL + Description: + Name: Composite packaging, plastic receptacle in plastic drum +- Code: YM + Description: + Name: Composite packaging, plastic receptacle in solid plastic box +- Code: YN + Description: + Name: Composite packaging, glass receptacle in steel drum +- Code: YP + Description: + Name: Composite packaging, glass receptacle in steel crate box +- Code: YQ + Description: + Name: Composite packaging, glass receptacle in aluminium drum +- Code: YR + Description: + Name: Composite packaging, glass receptacle in aluminium crate +- Code: YS + Description: + Name: Composite packaging, glass receptacle in wooden box +- Code: YT + Description: + Name: Composite packaging, glass receptacle in plywood drum +- Code: YV + Description: + Name: Composite packaging, glass receptacle in wickerwork hamper +- Code: YW + Description: + Name: Composite packaging, glass receptacle in fibre drum +- Code: YX + Description: + Name: Composite packaging, glass receptacle in fibreboard box +- Code: YY + Description: + Name: Composite packaging, glass receptacle in expandable plastic pack +- Code: YZ + Description: + Name: Composite packaging, glass receptacle in solid plastic pack +- Code: ZA + Description: + Name: Intermediate bulk container, paper, multi-wall +- Code: ZB + Description: + Name: Bag, large +- Code: ZC + Description: + Name: Intermediate bulk container, paper, multi-wall, water resistant +- Code: ZD + Description: + Name: Intermediate bulk container, rigid plastic, with structural equipment, solids +- Code: ZF + Description: + Name: Intermediate bulk container, rigid plastic, freestanding, solids +- Code: ZG + Description: + Name: Intermediate bulk container, rigid plastic, with structural equipment, pressurised +- Code: ZH + Description: + Name: Intermediate bulk container, rigid plastic, freestanding, pressurised +- Code: ZJ + Description: + Name: Intermediate bulk container, rigid plastic, with structural equipment, liquids +- Code: ZK + Description: + Name: Intermediate bulk container, rigid plastic, freestanding, liquids +- Code: ZL + Description: + Name: Intermediate bulk container, composite, rigid plastic, solids +- Code: ZM + Description: + Name: Intermediate bulk container, composite, flexible plastic, solids +- Code: ZN + Description: + Name: Intermediate bulk container, composite, rigid plastic, pressurised +- Code: ZP + Description: + Name: Intermediate bulk container, composite, flexible plastic, pressurised +- Code: ZQ + Description: + Name: Intermediate bulk container, composite, rigid plastic, liquids +- Code: ZR + Description: + Name: Intermediate bulk container, composite, flexible plastic, liquids +- Code: ZS + Description: + Name: Intermediate bulk container, composite +- Code: ZT + Description: + Name: Intermediate bulk container, fibreboard +- Code: ZU + Description: + Name: Intermediate bulk container, flexible +- Code: ZV + Description: + Name: Intermediate bulk container, metal, other than steel +- Code: ZW + Description: + Name: Intermediate bulk container, natural wood +- Code: ZX + Description: + Name: Intermediate bulk container, plywood +- Code: ZY + Description: + Name: Intermediate bulk container, reconstituted wood +- Code: ZZ + Description: + Name: Mutually defined +- Code: '13' + Description: + Name: Flexible IBC for solids, filled or discharged by gravity (liquids and solids + under pressure [>0.1 bar] are not permitted) +- Code: 13H1 + Description: + Name: Woven PP fabric without coating or inner liner +- Code: 13H2 + Description: + Name: Woven PP fabric, coated +- Code: 13H3 + Description: + Name: Woven PP fabric with inner liner +- Code: 13H4 + Description: + Name: Woven PP fabric, coated and with inner liner diff --git a/db/migrate/001_create_users.rb b/db/migrate/001_create_users.rb index ab4a560a..3bdd0952 100644 --- a/db/migrate/001_create_users.rb +++ b/db/migrate/001_create_users.rb @@ -4,30 +4,30 @@ class CreateUsers < ActiveRecord::Migration[4.2] def self.up create_table :users do |t| - t.column :nick, :string, :null => false - t.column :password_hash, :string, :null => false - t.column :password_salt, :string, :null => false - t.column :first_name, :string, :null => false - t.column :last_name, :string, :null => false - t.column :email, :string, :null => false + t.column :nick, :string, null: false + t.column :password_hash, :string, null: false + t.column :password_salt, :string, null: false + t.column :first_name, :string, null: false + t.column :last_name, :string, null: false + t.column :email, :string, null: false t.column :phone, :string t.column :address, :string - t.column :created_on, :timestamp, :null => false + t.column :created_on, :timestamp, null: false end - add_index(:users, :nick, :unique => true) - add_index(:users, :email, :unique => true) + add_index(:users, :nick, unique: true) + add_index(:users, :email, unique: true) # Create the default admin user... puts "Creating user #{USER_ADMIN} with password 'secret'..." - user = User.new(:nick => USER_ADMIN, :first_name => "Anton", :last_name => "Administrator", :email => "admin@foo.test") - user.password = "secret" - raise "Failed!" unless user.save && User.find_by_nick(USER_ADMIN).has_password("secret") + user = User.new(nick: USER_ADMIN, first_name: 'Anton', last_name: 'Administrator', email: 'admin@foo.test') + user.password = 'secret' + raise 'Failed!' unless user.save && User.find_by_nick(USER_ADMIN).has_password('secret') # Create a normal user... puts "Creating user #{USER_TEST} with password 'foobar'..." - user = User.new(:nick => USER_TEST, :first_name => "Tim", :last_name => "Tester", :email => "test@foo.test") - user.password = "foobar" - raise "Failed!" unless user.save && User.find_by_nick(USER_TEST).has_password("foobar") + user = User.new(nick: USER_TEST, first_name: 'Tim', last_name: 'Tester', email: 'test@foo.test') + user.password = 'foobar' + raise 'Failed!' unless user.save && User.find_by_nick(USER_TEST).has_password('foobar') end def self.down diff --git a/db/migrate/002_create_groups.rb b/db/migrate/002_create_groups.rb index bb7427b9..cffa19cc 100644 --- a/db/migrate/002_create_groups.rb +++ b/db/migrate/002_create_groups.rb @@ -4,47 +4,48 @@ class CreateGroups < ActiveRecord::Migration[4.2] def self.up create_table :groups do |t| - t.column :type, :string, :null => false # inheritance, types: Group, OrderGroup - t.column :name, :string, :null => false + t.column :type, :string, null: false # inheritance, types: Group, OrderGroup + t.column :name, :string, null: false t.column :description, :string t.column :actual_size, :integer # OrderGroup column - t.column :account_balance, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 # OrderGroup column + t.column :account_balance, :decimal, precision: 8, scale: 2, null: false, default: 0 # OrderGroup column t.column :account_updated, :timestamp # OrderGroup column - t.column :created_on, :timestamp, :null => false - t.column :role_admin, :boolean, :default => false, :null => false + t.column :created_on, :timestamp, null: false + t.column :role_admin, :boolean, default: false, null: false end - add_index(:groups, :name, :unique => true) + add_index(:groups, :name, unique: true) create_table :memberships do |t| - t.column :group_id, :integer, :null => false - t.column :user_id, :integer, :null => false + t.column :group_id, :integer, null: false + t.column :user_id, :integer, null: false end - add_index(:memberships, [:user_id, :group_id], :unique => true) + add_index(:memberships, %i[user_id group_id], unique: true) # Create the default "Administrators" group... puts "Creating group #{GROUP_ADMIN}..." - Group.create(:name => GROUP_ADMIN, :description => "System administrators.", :role_admin => true) + Group.create(name: GROUP_ADMIN, description: 'System administrators.', role_admin: true) raise 'Failed!' unless administrators = Group.find_by_name(GROUP_ADMIN) # Create a sample order group... puts "Creating order group #{GROUP_ORDER}..." - ordergroup = OrderGroup.create!(:name => GROUP_ORDER, :description => "A sample order group created by the migration.", :actual_size => 1, :account_updated => Time.now) - raise "Wrong type created!" unless ordergroup.is_a?(OrderGroup) + ordergroup = OrderGroup.create!(name: GROUP_ORDER, description: 'A sample order group created by the migration.', + actual_size: 1, account_updated: Time.now) + raise 'Wrong type created!' unless ordergroup.is_a?(OrderGroup) # Get the admin user and join the admin group... raise "User #{CreateUsers::USER_ADMIN} not found, cannot join group '#{administrators.name}'!" unless admin = User.find_by_nick(CreateUsers::USER_ADMIN) puts "Joining #{CreateUsers::USER_ADMIN} user to new '#{administrators.name}' group as a group admin..." - membership = Membership.create(:group => administrators, :user => admin) - raise "Failed!" unless admin.memberships.first == membership + membership = Membership.create(group: administrators, user: admin) + raise 'Failed!' unless admin.memberships.first == membership raise "User #{CreateUsers::USER_ADMIN} has no admin_roles" unless admin.role_admin? # Get the test user and join the order group... raise "User #{CreateUsers::USER_TEST} not found, cannot join group '#{ordergroup.name}'!" unless test = User.find_by_nick(CreateUsers::USER_TEST) puts "Joining #{CreateUsers::USER_TEST} user to new '#{ordergroup.name}' group as a group admin..." - membership = Membership.create(:group => ordergroup, :user => test) - raise "Failed!" unless test.memberships.first == membership + membership = Membership.create(group: ordergroup, user: test) + raise 'Failed!' unless test.memberships.first == membership end def self.down diff --git a/db/migrate/003_create_suppliers.rb b/db/migrate/003_create_suppliers.rb index 2b38c9c1..72e148b8 100644 --- a/db/migrate/003_create_suppliers.rb +++ b/db/migrate/003_create_suppliers.rb @@ -2,17 +2,17 @@ class CreateSuppliers < ActiveRecord::Migration[4.2] SUPPLIER_SAMPLE = 'Sample Supplier' def self.up - add_column :groups, :role_suppliers, :boolean, :default => false, :null => false + add_column :groups, :role_suppliers, :boolean, default: false, null: false Group.reset_column_information puts "Give #{CreateGroups::GROUP_ADMIN} the role supplier .." - raise "Failed" unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_suppliers, true) - raise "Cannot find admin user!" unless admin = User.find_by_nick(CreateUsers::USER_ADMIN) - raise "Failed to enable role_suppliers with admin user!" unless admin.role_suppliers? + raise 'Failed' unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_suppliers, true) + raise 'Cannot find admin user!' unless admin = User.find_by_nick(CreateUsers::USER_ADMIN) + raise 'Failed to enable role_suppliers with admin user!' unless admin.role_suppliers? create_table :suppliers do |t| - t.column :name, :string, :null => false - t.column :address, :string, :null => false - t.column :phone, :string, :null => false + t.column :name, :string, null: false + t.column :address, :string, null: false + t.column :phone, :string, null: false t.column :phone2, :string t.column :fax, :string t.column :email, :string @@ -23,12 +23,12 @@ def self.up t.column :order_howto, :string t.column :note, :string end - add_index(:suppliers, :name, :unique => true) + add_index(:suppliers, :name, unique: true) # Create sample supplier... puts "Creating sample supplier '#{SUPPLIER_SAMPLE}'..." - Supplier.create(:name => SUPPLIER_SAMPLE, :address => "Organic City", :phone => "0123-555555") - raise "Failed!" unless supplier = Supplier.find_by_name(SUPPLIER_SAMPLE) + Supplier.create(name: SUPPLIER_SAMPLE, address: 'Organic City', phone: '0123-555555') + raise 'Failed!' unless supplier = Supplier.find_by_name(SUPPLIER_SAMPLE) end def self.down diff --git a/db/migrate/004_create_article_meta.rb b/db/migrate/004_create_article_meta.rb index eb81f550..36c22f65 100644 --- a/db/migrate/004_create_article_meta.rb +++ b/db/migrate/004_create_article_meta.rb @@ -5,24 +5,24 @@ class CreateArticleMeta < ActiveRecord::Migration[4.2] def self.up # Add user roles... - add_column :groups, :role_article_meta, :boolean, :default => false, :null => false + add_column :groups, :role_article_meta, :boolean, default: false, null: false Group.reset_column_information puts "Give #{CreateGroups::GROUP_ADMIN} the role article_meta .." - raise "Failed" unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_article_meta, true) + raise 'Failed' unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_article_meta, true) raise 'Cannot find admin user!' unless admin = User.find_by_nick(CreateUsers::USER_ADMIN) raise 'Failed to enable role_article_meta with admin user!' unless admin.role_article_meta? # ArticleCategories create_table :article_categories do |t| - t.column :name, :string, :null => false + t.column :name, :string, null: false t.column :description, :string end - add_index(:article_categories, :name, :unique => true) + add_index(:article_categories, :name, unique: true) # Create sample category... puts "Creating sample article category '#{CATEGORY_SAMPLE}'..." - ArticleCategory.create(:name => CATEGORY_SAMPLE, :description => "This is just a sample article category.") - raise "Failed!" unless category = ArticleCategory.find_by_name(CATEGORY_SAMPLE) + ArticleCategory.create(name: CATEGORY_SAMPLE, description: 'This is just a sample article category.') + raise 'Failed!' unless category = ArticleCategory.find_by_name(CATEGORY_SAMPLE) end def self.down diff --git a/db/migrate/005_create_financial_transactions.rb b/db/migrate/005_create_financial_transactions.rb index 0b1cef89..7ca0e83c 100644 --- a/db/migrate/005_create_financial_transactions.rb +++ b/db/migrate/005_create_financial_transactions.rb @@ -2,19 +2,19 @@ class CreateFinancialTransactions < ActiveRecord::Migration[4.2] def self.up # Create Financial Transactions create_table :financial_transactions do |t| - t.column :order_group_id, :integer, :null => false - t.column :amount, :decimal, :precision => 8, :scale => 2, :null => false - t.column :note, :text, :null => false - t.column :user_id, :integer, :null => false - t.column :created_on, :datetime, :null => false + t.column :order_group_id, :integer, null: false + t.column :amount, :decimal, precision: 8, scale: 2, null: false + t.column :note, :text, null: false + t.column :user_id, :integer, null: false + t.column :created_on, :datetime, null: false end # add column for the finance role puts 'add column in "groups" for the finance role' - add_column :groups, :role_finance, :boolean, :default => false, :null => false + add_column :groups, :role_finance, :boolean, default: false, null: false Group.reset_column_information puts "Give #{CreateGroups::GROUP_ADMIN} the role finance .." - raise "Failed" unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_finance, true) + raise 'Failed' unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_finance, true) raise 'Cannot find admin user!' unless admin = User.find_by_nick(CreateUsers::USER_ADMIN) raise 'Failed to enable role_finance with admin user!' unless admin.role_finance? @@ -27,8 +27,8 @@ def self.up ordergroup.addFinancialTransaction(i, "Sample Transaction Nr. #{i}", admin) balance += i end - raise "Failed!" unless financial_transaction = FinancialTransaction.find_by_note('Sample Transaction Nr. 1') - raise "Failed to update account_balance!" unless OrderGroup.find(ordergroup.id).account_balance == balance + raise 'Failed!' unless financial_transaction = FinancialTransaction.find_by_note('Sample Transaction Nr. 1') + raise 'Failed to update account_balance!' unless OrderGroup.find(ordergroup.id).account_balance == balance end def self.down diff --git a/db/migrate/006_create_articles.rb b/db/migrate/006_create_articles.rb index 9a43c8dc..fd11e659 100644 --- a/db/migrate/006_create_articles.rb +++ b/db/migrate/006_create_articles.rb @@ -1,17 +1,17 @@ class CreateArticles < ActiveRecord::Migration[4.2] - SAMPLE_ARTICLE_NAMES = ['banana', 'kiwi', 'strawberry'] + SAMPLE_ARTICLE_NAMES = %w[banana kiwi strawberry] def self.up create_table :articles do |t| - t.column :name, :string, :null => false - t.column :supplier_id, :integer, :null => false - t.column :article_category_id, :integer, :null => false - t.column :unit, :string, :null => false + t.column :name, :string, null: false + t.column :supplier_id, :integer, null: false + t.column :article_category_id, :integer, null: false + t.column :unit, :string, null: false t.column :note, :string - t.column :availability, :boolean, :default => true, :null => false + t.column :availability, :boolean, default: true, null: false t.column :current_price_id, :integer end - add_index(:articles, :name, :unique => true) + add_index(:articles, :name, unique: true) # Create 30 sample articles... puts "Create 3 articles of the supplier '#{CreateSuppliers::SUPPLIER_SAMPLE}'..." @@ -20,14 +20,14 @@ def self.up SAMPLE_ARTICLE_NAMES.each do |a| puts 'Create Article ' + a - Article.create(:name => a, - :supplier => supplier, - :article_category => category, - :unit => '500g', - :note => 'delicious', - :availability => true) + Article.create(name: a, + supplier: supplier, + article_category: category, + unit: '500g', + note: 'delicious', + availability: true) end - raise "Failed!" unless Article.find(:all).length == SAMPLE_ARTICLE_NAMES.length + raise 'Failed!' unless Article.find(:all).length == SAMPLE_ARTICLE_NAMES.length end def self.down diff --git a/db/migrate/007_create_article_prices.rb b/db/migrate/007_create_article_prices.rb index 32488794..68ed0915 100644 --- a/db/migrate/007_create_article_prices.rb +++ b/db/migrate/007_create_article_prices.rb @@ -1,13 +1,13 @@ class CreateArticlePrices < ActiveRecord::Migration[4.2] def self.up create_table :article_prices do |t| - t.column :article_id, :int, :null => false - t.column :clear_price, :decimal, :precision => 8, :scale => 2, :null => false - t.column :gross_price, :decimal, :precision => 8, :scale => 2, :null => false # gross price, incl. vat, refund and price markup - t.column :tax, :float, :null => false, :default => 0 - t.column :refund, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 + t.column :article_id, :int, null: false + t.column :clear_price, :decimal, precision: 8, scale: 2, null: false + t.column :gross_price, :decimal, precision: 8, scale: 2, null: false # gross price, incl. vat, refund and price markup + t.column :tax, :float, null: false, default: 0 + t.column :refund, :decimal, precision: 8, scale: 2, null: false, default: 0 t.column :updated_on, :datetime - t.column :unit_quantity, :int, :default => 1, :null => false + t.column :unit_quantity, :int, default: 1, null: false t.column :order_number, :string end add_index(:article_prices, :article_id) @@ -18,11 +18,11 @@ def self.up puts 'Create Price for article ' + a raise 'article #{a} not found!' unless article = Article.find_by_name(a) - new_price = ArticlePrice.new(:clear_price => rand(4) + 1, - :tax => 7.0, - :refund => 0, - :unit_quantity => rand(10) + 1, - :order_number => rand(9999)) + new_price = ArticlePrice.new(clear_price: rand(1..4), + tax: 7.0, + refund: 0, + unit_quantity: rand(1..10), + order_number: rand(9999)) article.add_price(new_price) raise 'Failed!' unless ArticlePrice.find_by_article_id(article.id) end diff --git a/db/migrate/008_create_orders.rb b/db/migrate/008_create_orders.rb index 6eb8c921..0106fc32 100644 --- a/db/migrate/008_create_orders.rb +++ b/db/migrate/008_create_orders.rb @@ -4,28 +4,28 @@ class CreateOrders < ActiveRecord::Migration[4.2] def self.up # Order role - add_column :groups, :role_orders, :boolean, :default => false, :null => false + add_column :groups, :role_orders, :boolean, default: false, null: false Group.reset_column_information puts "Give #{CreateGroups::GROUP_ADMIN} the role finance .." - raise "Failed" unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_orders, true) + raise 'Failed' unless Group.find_by_name(CreateGroups::GROUP_ADMIN).update_attribute(:role_orders, true) raise 'Cannot find admin user!' unless admin = User.find_by_nick(CreateUsers::USER_ADMIN) raise 'Failed to enable role_orders with admin user!' unless admin.role_orders? # Create the default "Order" group... puts 'Creating group "Orders"...' - Group.create(:name => GROUP_ORDER, :description => "working group for managing orders", :role_orders => true) - raise "Failed!" unless Group.find_by_name(GROUP_ORDER) + Group.create(name: GROUP_ORDER, description: 'working group for managing orders', role_orders: true) + raise 'Failed!' unless Group.find_by_name(GROUP_ORDER) # Order create_table :orders do |t| - t.column :name, :string, :null => false - t.column :supplier_id, :integer, :null => false - t.column :starts, :datetime, :null => false + t.column :name, :string, null: false + t.column :supplier_id, :integer, null: false + t.column :starts, :datetime, null: false t.column :ends, :datetime t.column :note, :string - t.column :finished, :boolean, :default => false, :null => false - t.column :booked, :boolean, :null => false, :default => false - t.column :lock_version, :integer, :null => false, :default => 0 + t.column :finished, :boolean, default: false, null: false + t.column :booked, :boolean, null: false, default: false + t.column :lock_version, :integer, null: false, default: 0 t.column :updated_by_user_id, :integer end add_index(:orders, :starts) @@ -35,74 +35,76 @@ def self.up puts "Creating order '#{ORDER_TEST}'..." raise "Supplier '#{CreateSuppliers::SUPPLIER_SAMPLE}' not found!" unless supplier = Supplier.find_by_name(CreateSuppliers::SUPPLIER_SAMPLE) - Order.create(:name => ORDER_TEST, :supplier => supplier, :starts => Time.now) + Order.create(name: ORDER_TEST, supplier: supplier, starts: Time.now) raise 'Creating test order failed!' unless order = Order.find_by_name(ORDER_TEST) # OrderArticle create_table :order_articles do |t| - t.column :order_id, :integer, :null => false - t.column :article_id, :integer, :null => false - t.column :quantity, :integer, :null => false, :default => 0 - t.column :tolerance, :integer, :null => false, :default => 0 - t.column :units_to_order, :integer, :null => false, :default => 0 - t.column :lock_version, :integer, :null => false, :default => 0 + t.column :order_id, :integer, null: false + t.column :article_id, :integer, null: false + t.column :quantity, :integer, null: false, default: 0 + t.column :tolerance, :integer, null: false, default: 0 + t.column :units_to_order, :integer, null: false, default: 0 + t.column :lock_version, :integer, null: false, default: 0 end - add_index(:order_articles, [:order_id, :article_id], :unique => true) + add_index(:order_articles, %i[order_id article_id], unique: true) puts 'Adding articles to the order...' - CreateArticles::SAMPLE_ARTICLE_NAMES.each { |a| + CreateArticles::SAMPLE_ARTICLE_NAMES.each do |a| puts "Article #{a}..." raise 'Article not found!' unless article = Article.find_by_name(a) raise 'No price found for article!' unless price = article.current_price - OrderArticle.create(:order => order, :article => article) + OrderArticle.create(order: order, article: article) raise 'Creating OrderArticle failed!' unless OrderArticle.find_by_order_id_and_article_id(order.id, article.id) - } + end raise 'Creating OrderArticles failed!' unless order.articles.size == CreateArticles::SAMPLE_ARTICLE_NAMES.length # GroupOrder create_table :group_orders do |t| - t.column :order_group_id, :integer, :null => false - t.column :order_id, :integer, :null => false - t.column :price, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 - t.column :lock_version, :integer, :null => false, :default => 0 - t.column :updated_on, :timestamp, :null => false - t.column :updated_by_user_id, :integer, :null => false + t.column :order_group_id, :integer, null: false + t.column :order_id, :integer, null: false + t.column :price, :decimal, precision: 8, scale: 2, null: false, default: 0 + t.column :lock_version, :integer, null: false, default: 0 + t.column :updated_on, :timestamp, null: false + t.column :updated_by_user_id, :integer, null: false end - add_index(:group_orders, [:order_group_id, :order_id], :unique => true) + add_index(:group_orders, %i[order_group_id order_id], unique: true) puts 'Adding group order...' raise "Cannot find user #{CreateUsers::USER_TEST}" unless user = User.find_by_nick(CreateUsers::USER_TEST) raise "Cannot find OrderGroup '#{CreateGroups::GROUP_ORDER}'!" unless orderGroup = OrderGroup.find_by_name(CreateGroups::GROUP_ORDER) - GroupOrder.create(:order_group => orderGroup, :order => order, :price => 0, :updated_by => user) - raise 'Retrieving group order failed!' unless groupOrder = orderGroup.group_orders.find(:first, :conditions => "order_id = #{order.id}") + GroupOrder.create(order_group: orderGroup, order: order, price: 0, updated_by: user) + raise 'Retrieving group order failed!' unless groupOrder = orderGroup.group_orders.find(:first, + conditions: "order_id = #{order.id}") # GroupOrderArticles create_table :group_order_articles do |t| - t.column :group_order_id, :integer, :null => false - t.column :order_article_id, :integer, :null => false - t.column :quantity, :integer, :null => false - t.column :tolerance, :integer, :null => false - t.column :updated_on, :timestamp, :null => false + t.column :group_order_id, :integer, null: false + t.column :order_article_id, :integer, null: false + t.column :quantity, :integer, null: false + t.column :tolerance, :integer, null: false + t.column :updated_on, :timestamp, null: false end - add_index(:group_order_articles, [:group_order_id, :order_article_id], :unique => true, :name => "goa_index") + add_index(:group_order_articles, %i[group_order_id order_article_id], unique: true, name: 'goa_index') # GroupOrderArticleQuantity create_table :group_order_article_quantities do |t| - t.column :group_order_article_id, :int, :null => false - t.column :quantity, :int, :default => 0 - t.column :tolerance, :int, :default => 0 - t.column :created_on, :timestamp, :null => false + t.column :group_order_article_id, :int, null: false + t.column :quantity, :int, default: 0 + t.column :tolerance, :int, default: 0 + t.column :created_on, :timestamp, null: false end puts 'Adding articles to group order...' - order.order_articles.each { |orderArticle| + order.order_articles.each do |orderArticle| puts "Article #{orderArticle.article.name}..." - GroupOrderArticle.create(:group_order => groupOrder, :order_article => orderArticle, :quantity => 0, :tolerance => 0) - raise 'Failed to create order!' unless article = GroupOrderArticle.find(:first, :conditions => "group_order_id = #{groupOrder.id} AND order_article_id = #{orderArticle.id}") + GroupOrderArticle.create(group_order: groupOrder, order_article: orderArticle, quantity: 0, tolerance: 0) + raise 'Failed to create order!' unless article = GroupOrderArticle.find(:first, + conditions: "group_order_id = #{groupOrder.id} AND order_article_id = #{orderArticle.id}") - article.updateQuantities(rand(6) + 1, rand(4) + 1) - } + article.updateQuantities(rand(1..6), rand(1..4)) + end raise 'Failed to create orders!' unless groupOrder.order_articles.size == order.order_articles.size groupOrder.updatePrice diff --git a/db/migrate/009_create_order_results.rb b/db/migrate/009_create_order_results.rb index 20b75193..6b1cc65a 100644 --- a/db/migrate/009_create_order_results.rb +++ b/db/migrate/009_create_order_results.rb @@ -1,32 +1,32 @@ class CreateOrderResults < ActiveRecord::Migration[4.2] def self.up create_table :group_order_results do |t| - t.column :order_id, :int, :null => false - t.column :group_name, :string, :null => false - t.column :price, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 + t.column :order_id, :int, null: false + t.column :group_name, :string, null: false + t.column :price, :decimal, precision: 8, scale: 2, null: false, default: 0 end - add_index(:group_order_results, [:group_name, :order_id], :unique => true) + add_index(:group_order_results, %i[group_name order_id], unique: true) create_table :order_article_results do |t| - t.column :order_id, :int, :null => false - t.column :name, :string, :null => false - t.column :unit, :string, :null => false + t.column :order_id, :int, null: false + t.column :name, :string, null: false + t.column :unit, :string, null: false t.column :note, :string - t.column :clear_price, :decimal, :precision => 8, :scale => 2, :null => false - t.column :gross_price, :decimal, :precision => 8, :scale => 2, :null => false - t.column :tax, :float, :null => false, :default => 0 - t.column :refund, :decimal, :precision => 8, :scale => 2 - t.column :fc_markup, :float, :null => false + t.column :clear_price, :decimal, precision: 8, scale: 2, null: false + t.column :gross_price, :decimal, precision: 8, scale: 2, null: false + t.column :tax, :float, null: false, default: 0 + t.column :refund, :decimal, precision: 8, scale: 2 + t.column :fc_markup, :float, null: false t.column :order_number, :string - t.column :unit_quantity, :int, :null => false - t.column :units_to_order, :int, :null => false + t.column :unit_quantity, :int, null: false + t.column :units_to_order, :int, null: false end add_index(:order_article_results, :order_id) create_table :group_order_article_results do |t| - t.column :order_article_result_id, :int, :null => false - t.column :group_order_result_id, :int, :null => false - t.column :quantity, :int, :null => false + t.column :order_article_result_id, :int, null: false + t.column :group_order_result_id, :int, null: false + t.column :quantity, :int, null: false t.column :tolerance, :int end add_index(:group_order_article_results, :order_article_result_id) diff --git a/db/migrate/011_create_comments.rb b/db/migrate/011_create_comments.rb index 28fc0428..55148d08 100644 --- a/db/migrate/011_create_comments.rb +++ b/db/migrate/011_create_comments.rb @@ -1,16 +1,16 @@ class CreateComments < ActiveRecord::Migration[4.2] def self.up - create_table :comments, :force => true do |t| - t.column :title, :string, :limit => 50, :default => "" - t.column :comment, :string, :default => "" - t.column :created_at, :datetime, :null => false - t.column :commentable_id, :integer, :default => 0, :null => false - t.column :commentable_type, :string, :limit => 15, - :default => "", :null => false - t.column :user_id, :integer, :default => 0, :null => false + create_table :comments, force: true do |t| + t.column :title, :string, limit: 50, default: '' + t.column :comment, :string, default: '' + t.column :created_at, :datetime, null: false + t.column :commentable_id, :integer, default: 0, null: false + t.column :commentable_type, :string, limit: 15, + default: '', null: false + t.column :user_id, :integer, default: 0, null: false end - add_index :comments, ["user_id"], :name => "fk_comments_user" + add_index :comments, ['user_id'], name: 'fk_comments_user' end def self.down diff --git a/db/migrate/012_create_order_clearing.rb b/db/migrate/012_create_order_clearing.rb index 1d3133fd..9ddb4ad3 100644 --- a/db/migrate/012_create_order_clearing.rb +++ b/db/migrate/012_create_order_clearing.rb @@ -1,8 +1,8 @@ class CreateOrderClearing < ActiveRecord::Migration[4.2] def self.up - add_column :orders, :invoice_amount, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 - add_column :orders, :refund, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 - add_column :orders, :refund_credit, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 + add_column :orders, :invoice_amount, :decimal, precision: 8, scale: 2, null: false, default: 0 + add_column :orders, :refund, :decimal, precision: 8, scale: 2, null: false, default: 0 + add_column :orders, :refund_credit, :decimal, precision: 8, scale: 2, null: false, default: 0 add_column :orders, :invoice_number, :string add_column :orders, :invoice_date, :string end diff --git a/db/migrate/013_add_messaging.rb b/db/migrate/013_add_messaging.rb index 84ba8d6f..7f01b2dd 100644 --- a/db/migrate/013_add_messaging.rb +++ b/db/migrate/013_add_messaging.rb @@ -3,13 +3,13 @@ def self.up # Table that holds the messages: create_table :messages do |t| t.column :sender_id, :integer - t.column :recipient_id, :integer, :null => false - t.column :recipients, :string, :null => false - t.column :subject, :string, :null => false - t.column :body, :text, :null => false - t.column :read, :boolean, :null => false, :default => false - t.column :email_state, :integer, :null => false - t.column :created_on, :timestamp, :null => false + t.column :recipient_id, :integer, null: false + t.column :recipients, :string, null: false + t.column :subject, :string, null: false + t.column :body, :text, null: false + t.column :read, :boolean, null: false, default: false + t.column :email_state, :integer, null: false + t.column :created_on, :timestamp, null: false end add_index(:messages, :sender_id) add_index(:messages, :recipient_id) diff --git a/db/migrate/014_create_tasks.rb b/db/migrate/014_create_tasks.rb index db878546..8872523e 100644 --- a/db/migrate/014_create_tasks.rb +++ b/db/migrate/014_create_tasks.rb @@ -1,26 +1,26 @@ class CreateTasks < ActiveRecord::Migration[4.2] def self.up create_table :tasks do |t| - t.column :name, :string, :null => false + t.column :name, :string, null: false t.column :description, :string t.column :due_date, :date - t.column :done, :boolean, :default => false + t.column :done, :boolean, default: false t.column :group_id, :integer - t.column :assigned, :boolean, :default => false - t.column :created_on, :datetime, :null => false - t.column :updated_on, :datetime, :null => false + t.column :assigned, :boolean, default: false + t.column :created_on, :datetime, null: false + t.column :updated_on, :datetime, null: false end add_index :tasks, :name add_index :tasks, :due_date create_table :assignments do |t| - t.column :user_id, :integer, :null => false - t.column :task_id, :integer, :null => false - t.column :accepted, :boolean, :default => false + t.column :user_id, :integer, null: false + t.column :task_id, :integer, null: false + t.column :accepted, :boolean, default: false end - add_index :assignments, [:user_id, :task_id], :unique => true + add_index :assignments, %i[user_id task_id], unique: true - add_column :groups, :weekly_task, :boolean, :default => false # if group has an job for every week + add_column :groups, :weekly_task, :boolean, default: false # if group has an job for every week add_column :groups, :weekday, :integer # e.g. 1 means monday, 2 = tuesday an so on add_column :groups, :task_name, :string # the name of the weekly task add_column :groups, :task_description, :string diff --git a/db/migrate/015_change_result_quantities.rb b/db/migrate/015_change_result_quantities.rb index 23731334..56020eab 100644 --- a/db/migrate/015_change_result_quantities.rb +++ b/db/migrate/015_change_result_quantities.rb @@ -1,11 +1,11 @@ class ChangeResultQuantities < ActiveRecord::Migration[4.2] def self.up - change_column :group_order_article_results, :quantity, :decimal, :precision => 6, :scale => 3 - change_column :order_article_results, :units_to_order, :decimal, :precision => 6, :scale => 3, :null => false + change_column :group_order_article_results, :quantity, :decimal, precision: 6, scale: 3 + change_column :order_article_results, :units_to_order, :decimal, precision: 6, scale: 3, null: false end def self.down - change_column :group_order_article_results, :quantity, :integer, :null => false - change_column :order_article_results, :units_to_order, :integer, :default => 0, :null => false + change_column :group_order_article_results, :quantity, :integer, null: false + change_column :order_article_results, :units_to_order, :integer, default: 0, null: false end end diff --git a/db/migrate/018_create_invites.rb b/db/migrate/018_create_invites.rb index cc8a1ebc..49c3edf9 100644 --- a/db/migrate/018_create_invites.rb +++ b/db/migrate/018_create_invites.rb @@ -1,11 +1,11 @@ class CreateInvites < ActiveRecord::Migration[4.2] def self.up create_table :invites do |t| - t.column :token, :string, :null => false - t.column :expires_at, :timestamp, :null => false - t.column :group_id, :integer, :null => false - t.column :user_id, :integer, :null => false - t.column :email, :string, :null => false + t.column :token, :string, null: false + t.column :expires_at, :timestamp, null: false + t.column :group_id, :integer, null: false + t.column :user_id, :integer, null: false + t.column :email, :string, null: false end add_index :invites, :token end diff --git a/db/migrate/019_remove_uniqueness_of_article_name.rb b/db/migrate/019_remove_uniqueness_of_article_name.rb index 7504a66a..50170cb8 100644 --- a/db/migrate/019_remove_uniqueness_of_article_name.rb +++ b/db/migrate/019_remove_uniqueness_of_article_name.rb @@ -1,11 +1,11 @@ class RemoveUniquenessOfArticleName < ActiveRecord::Migration[4.2] def self.up remove_index :articles, :name - add_index :articles, [:name, :supplier_id] + add_index :articles, %i[name supplier_id] end def self.down - remove_index :articles, [:name, :supplier_id] - add_index :articles, :name, :unique => true + remove_index :articles, %i[name supplier_id] + add_index :articles, :name, unique: true end end diff --git a/db/migrate/021_remove_table_article_prices.rb b/db/migrate/021_remove_table_article_prices.rb index dc664347..fdbf312d 100644 --- a/db/migrate/021_remove_table_article_prices.rb +++ b/db/migrate/021_remove_table_article_prices.rb @@ -1,71 +1,71 @@ class RemoveTableArticlePrices < ActiveRecord::Migration[4.2] def self.up - puts "create columns in articles ..." - add_column "articles", "clear_price", :decimal, :precision => 8, :scale => 2, :default => 0.0, :null => false - add_column "articles", "gross_price", :decimal, :precision => 8, :scale => 2, :default => 0.0, :null => false - add_column "articles", "tax", :float - add_column "articles", "refund", :decimal, :precision => 8, :scale => 2, :default => 0.0, :null => false - add_column "articles", "unit_quantity", :integer, :default => 1, :null => false - add_column "articles", "order_number", :string - add_column "articles", "created_at", :datetime - add_column "articles", "updated_at", :datetime + puts 'create columns in articles ...' + add_column 'articles', 'clear_price', :decimal, precision: 8, scale: 2, default: 0.0, null: false + add_column 'articles', 'gross_price', :decimal, precision: 8, scale: 2, default: 0.0, null: false + add_column 'articles', 'tax', :float + add_column 'articles', 'refund', :decimal, precision: 8, scale: 2, default: 0.0, null: false + add_column 'articles', 'unit_quantity', :integer, default: 1, null: false + add_column 'articles', 'order_number', :string + add_column 'articles', 'created_at', :datetime + add_column 'articles', 'updated_at', :datetime # stop auto-updating the timestamps to make the data-copy safe! Article.record_timestamps = false - puts "now copy values of article_prices into new articles-columns..." + puts 'now copy values of article_prices into new articles-columns...' Article.find(:all).each do |article| price = article.current_price - article.update_attributes!(:clear_price => price.clear_price, - :gross_price => price.gross_price, - :tax => price.tax, - :refund => price.refund, - :unit_quantity => price.unit_quantity, - :order_number => price.order_number, - :updated_at => price.updated_on, - :created_at => price.updated_on) + article.update_attributes!(clear_price: price.clear_price, + gross_price: price.gross_price, + tax: price.tax, + refund: price.refund, + unit_quantity: price.unit_quantity, + order_number: price.order_number, + updated_at: price.updated_on, + created_at: price.updated_on) end - puts "delete article_prices, current_price attribute" + puts 'delete article_prices, current_price attribute' drop_table :article_prices remove_column :articles, :current_price_id end def self.down add_column :articles, :current_price_id, :integer - create_table "article_prices", :force => true do |t| - t.integer "article_id", :default => 0, :null => false - t.decimal "clear_price", :precision => 8, :scale => 2, :default => 0.0, :null => false - t.decimal "gross_price", :precision => 8, :scale => 2, :default => 0.0, :null => false - t.float "tax", :default => 0.0, :null => false - t.decimal "refund", :precision => 8, :scale => 2, :default => 0.0, :null => false - t.datetime "updated_on" - t.integer "unit_quantity", :default => 1, :null => false - t.string "order_number" + create_table 'article_prices', force: true do |t| + t.integer 'article_id', default: 0, null: false + t.decimal 'clear_price', precision: 8, scale: 2, default: 0.0, null: false + t.decimal 'gross_price', precision: 8, scale: 2, default: 0.0, null: false + t.float 'tax', default: 0.0, null: false + t.decimal 'refund', precision: 8, scale: 2, default: 0.0, null: false + t.datetime 'updated_on' + t.integer 'unit_quantity', default: 1, null: false + t.string 'order_number' end # copy data from article now into old ArticlePrice-object Article.find(:all).each do |article| - price = ArticlePrice.create(:clear_price => article.clear_price, - :gross_price => article.gross_price, - :tax => article.tax, - :refund => article.refund, - :unit_quantity => article.unit_quantity, - :order_number => article.order_number.blank? ? nil : article.order_number, - :updated_on => article.updated_at) + price = ArticlePrice.create(clear_price: article.clear_price, + gross_price: article.gross_price, + tax: article.tax, + refund: article.refund, + unit_quantity: article.unit_quantity, + order_number: article.order_number.presence, + updated_on: article.updated_at) article.update_attribute(:current_price, price) price.update_attribute(:article, article) end # remove new columns - remove_column "articles", "clear_price" - remove_column "articles", "gross_price" - remove_column "articles", "tax" - remove_column "articles", "refund" - remove_column "articles", "unit_quantity" - remove_column "articles", "order_number" - remove_column "articles", "created_at" - remove_column "articles", "updated_at" + remove_column 'articles', 'clear_price' + remove_column 'articles', 'gross_price' + remove_column 'articles', 'tax' + remove_column 'articles', 'refund' + remove_column 'articles', 'unit_quantity' + remove_column 'articles', 'order_number' + remove_column 'articles', 'created_at' + remove_column 'articles', 'updated_at' end end diff --git a/db/migrate/022_add_required_user_for_task.rb b/db/migrate/022_add_required_user_for_task.rb index 9e8d9621..105e1593 100644 --- a/db/migrate/022_add_required_user_for_task.rb +++ b/db/migrate/022_add_required_user_for_task.rb @@ -1,7 +1,7 @@ class AddRequiredUserForTask < ActiveRecord::Migration[4.2] def self.up - add_column :tasks, :required_users, :integer, :default => 1 - add_column :groups, :task_required_users, :integer, :default => 1 + add_column :tasks, :required_users, :integer, default: 1 + add_column :groups, :task_required_users, :integer, default: 1 # add default values to every task and group Task.find(:all).each { |task| task.update_attribute :required_users, 1 } Group.workgroups.each { |group| group.update_attribute :task_required_users, 1 } diff --git a/db/migrate/024_add_deposit_defaults.rb b/db/migrate/024_add_deposit_defaults.rb index ed9da063..68afdf5a 100644 --- a/db/migrate/024_add_deposit_defaults.rb +++ b/db/migrate/024_add_deposit_defaults.rb @@ -7,6 +7,5 @@ def self.up change_column_default :orders, :deposit_credit, 0.0 end - def self.down - end + def self.down; end end diff --git a/db/migrate/025_extend_comments.rb b/db/migrate/025_extend_comments.rb index 662b92fd..3b1b1da2 100644 --- a/db/migrate/025_extend_comments.rb +++ b/db/migrate/025_extend_comments.rb @@ -1,9 +1,9 @@ class ExtendComments < ActiveRecord::Migration[4.2] def self.up - change_column :comments, :comment, :text, :default => "" + change_column :comments, :comment, :text, default: '' end def self.down - change_column :comments, :comment, :string, :default => "" + change_column :comments, :comment, :string, default: '' end end diff --git a/db/migrate/20090120184410_road_to_version_three.rb b/db/migrate/20090120184410_road_to_version_three.rb index 4b8aa9ab..c7adfc87 100644 --- a/db/migrate/20090120184410_road_to_version_three.rb +++ b/db/migrate/20090120184410_road_to_version_three.rb @@ -10,10 +10,10 @@ def self.up create_table :messages do |t| t.references :sender t.text :recipients_ids - t.string :subject, :null => false + t.string :subject, null: false t.text :body - t.integer :email_state, :default => 0, :null => false - t.boolean :private, :default => false + t.integer :email_state, default: 0, null: false + t.boolean :private, default: false t.datetime :created_at end @@ -23,9 +23,9 @@ def self.up add_column :groups, :deleted_at, :datetime # == Workgroups - puts "Migrate all groups to workgroups.." - Group.find(:all, :conditions => { :type => "" }).each do |workgroup| - workgroup.update_attribute(:type, "Workgroup") + puts 'Migrate all groups to workgroups..' + Group.find(:all, conditions: { type: '' }).each do |workgroup| + workgroup.update_attribute(:type, 'Workgroup') end # == Ordergroups @@ -34,11 +34,11 @@ def self.up rename_column :financial_transactions, :order_group_id, :ordergroup_id rename_column :group_orders, :order_group_id, :ordergroup_id rename_column :tasks, :group_id, :workgroup_id - remove_index :group_orders, :name => "index_group_orders_on_order_group_id_and_order_id" - add_index :group_orders, [:ordergroup_id, :order_id], :unique => true + remove_index :group_orders, name: 'index_group_orders_on_order_group_id_and_order_id' + add_index :group_orders, %i[ordergroup_id order_id], unique: true - Group.find(:all, :conditions => { :type => "OrderGroup" }).each do |ordergroup| - ordergroup.update_attribute(:type, "Ordergroup") + Group.find(:all, conditions: { type: 'OrderGroup' }).each do |ordergroup| + ordergroup.update_attribute(:type, 'Ordergroup') end # move contact-infos from users to ordergroups add_column :groups, :contact_person, :string @@ -47,8 +47,8 @@ def self.up Ordergroup.all.each do |ordergroup| contact = ordergroup.users.first if contact - ordergroup.update_attributes :contact_person => contact.name, - :contact_phone => contact.phone, :contact_address => contact.address + ordergroup.update_attributes contact_person: contact.name, + contact_phone: contact.phone, contact_address: contact.address end end remove_column :users, :address @@ -58,15 +58,18 @@ def self.up drop_table :group_order_results drop_table :order_article_results drop_table :group_order_article_results - GroupOrder.delete_all; OrderArticle.delete_all; GroupOrderArticle.delete_all; GroupOrderArticleQuantity.delete_all + GroupOrder.delete_all + OrderArticle.delete_all + GroupOrderArticle.delete_all + GroupOrderArticleQuantity.delete_all create_table :orders do |t| t.references :supplier t.text :note t.datetime :starts t.datetime :ends - t.string :state, :default => "open" # Statemachine ... open -> finished -> closed - t.integer :lock_version, :default => 0, :null => false + t.string :state, default: 'open' # Statemachine ... open -> finished -> closed + t.integer :lock_version, default: 0, null: false t.integer :updated_by_user_id end @@ -79,9 +82,9 @@ def self.up t.date :date t.date :paid_on t.text :note - t.decimal :amount, :null => false, :precision => 8, :scale => 2, :default => 0.0 - t.decimal :deposit, :precision => 8, :scale => 2, :default => 0.0, :null => false - t.decimal :deposit_credit, :precision => 8, :scale => 2, :default => 0.0, :null => false + t.decimal :amount, null: false, precision: 8, scale: 2, default: 0.0 + t.decimal :deposit, precision: 8, scale: 2, default: 0.0, null: false + t.decimal :deposit_credit, precision: 8, scale: 2, default: 0.0, null: false t.timestamps end @@ -108,41 +111,41 @@ def self.up # == ArticlePrice create_table :article_prices do |t| t.references :article - t.decimal :price, :precision => 8, :scale => 2, :default => 0.0, :null => false - t.decimal :tax, :precision => 8, :scale => 2, :default => 0.0, :null => false - t.decimal :deposit, :precision => 8, :scale => 2, :default => 0.0, :null => false + t.decimal :price, precision: 8, scale: 2, default: 0.0, null: false + t.decimal :tax, precision: 8, scale: 2, default: 0.0, null: false + t.decimal :deposit, precision: 8, scale: 2, default: 0.0, null: false t.integer :unit_quantity t.datetime :created_at end # Create price history for every Article Article.all.each do |a| - a.article_prices.create :price => a.price, :tax => a.tax, - :deposit => a.deposit, :unit_quantity => a.unit_quantity + a.article_prices.create price: a.price, tax: a.tax, + deposit: a.deposit, unit_quantity: a.unit_quantity end # Every Article has now a Category. Fix it if neccessary. - Article.all(:conditions => { :article_category_id => nil }).each do |article| + Article.all(conditions: { article_category_id: nil }).each do |article| article.update_attribute(:article_category, ArticleCategory.first) end # order-articles add_column :order_articles, :article_price_id, :integer # == GroupOrder - change_column :group_orders, :updated_by_user_id, :integer, :default => nil, :null => true + change_column :group_orders, :updated_by_user_id, :integer, default: nil, null: true # == GroupOrderArticle # The total order result in ordergroup is now saved! - add_column :group_order_articles, :result, :integer, :default => nil + add_column :group_order_articles, :result, :integer, default: nil # == StockArticle add_column :articles, :type, :string - add_column :articles, :quantity, :integer, :default => 0 + add_column :articles, :quantity, :integer, default: 0 # == StockChanges create_table :stock_changes do |t| t.references :delivery t.references :order t.references :stock_article - t.integer :quantity, :default => 0 + t.integer :quantity, default: 0 t.datetime :created_at end @@ -159,6 +162,5 @@ def self.up User.all.each { |u| u.settings['notify.upcoming_tasks'] = 1 } end - def self.down - end + def self.down; end end diff --git a/db/migrate/20090317175355_add_profit_to_orders.rb b/db/migrate/20090317175355_add_profit_to_orders.rb index 59f79609..78013f7d 100644 --- a/db/migrate/20090317175355_add_profit_to_orders.rb +++ b/db/migrate/20090317175355_add_profit_to_orders.rb @@ -1,6 +1,6 @@ class AddProfitToOrders < ActiveRecord::Migration[4.2] def self.up - add_column :orders, :foodcoop_result, :decimal, :precision => 8, :scale => 2 + add_column :orders, :foodcoop_result, :decimal, precision: 8, scale: 2 Order.closed.each do |order| order.update_attribute(:foodcoop_result, order.profit) diff --git a/db/migrate/20090325175756_create_pages.foodsoft_wiki_engine.rb b/db/migrate/20090325175756_create_pages.foodsoft_wiki_engine.rb index c5692f35..d2221013 100644 --- a/db/migrate/20090325175756_create_pages.foodsoft_wiki_engine.rb +++ b/db/migrate/20090325175756_create_pages.foodsoft_wiki_engine.rb @@ -5,7 +5,7 @@ def self.up t.string :title t.text :body t.string :permalink - t.integer :lock_version, :default => 0 + t.integer :lock_version, default: 0 t.integer :updated_by t.integer :redirect t.integer :parent_id diff --git a/db/migrate/20090405131156_modify_group_order_article_result.rb b/db/migrate/20090405131156_modify_group_order_article_result.rb index 44e0dea8..b979422e 100644 --- a/db/migrate/20090405131156_modify_group_order_article_result.rb +++ b/db/migrate/20090405131156_modify_group_order_article_result.rb @@ -1,6 +1,6 @@ class ModifyGroupOrderArticleResult < ActiveRecord::Migration[4.2] def self.up - change_column :group_order_articles, :result, :decimal, :precision => 8, :scale => 3 + change_column :group_order_articles, :result, :decimal, precision: 8, scale: 3 end def self.down diff --git a/db/migrate/20090907120012_add_missing_indexes.rb b/db/migrate/20090907120012_add_missing_indexes.rb index 189b0417..93ea8771 100644 --- a/db/migrate/20090907120012_add_missing_indexes.rb +++ b/db/migrate/20090907120012_add_missing_indexes.rb @@ -1,35 +1,34 @@ class AddMissingIndexes < ActiveRecord::Migration[4.2] def self.up - add_index "article_prices", ["article_id"] + add_index 'article_prices', ['article_id'] - add_index "articles", ["supplier_id"] - add_index "articles", ["article_category_id"] - add_index "articles", ["type"] + add_index 'articles', ['supplier_id'] + add_index 'articles', ['article_category_id'] + add_index 'articles', ['type'] - add_index "deliveries", ["supplier_id"] + add_index 'deliveries', ['supplier_id'] - add_index "financial_transactions", ["ordergroup_id"] + add_index 'financial_transactions', ['ordergroup_id'] - add_index "group_order_article_quantities", ["group_order_article_id"] - add_index "group_orders", ["order_id"] - add_index "group_orders", ["ordergroup_id"] + add_index 'group_order_article_quantities', ['group_order_article_id'] + add_index 'group_orders', ['order_id'] + add_index 'group_orders', ['ordergroup_id'] - add_index "invoices", ["supplier_id"] - add_index "invoices", ["delivery_id"] + add_index 'invoices', ['supplier_id'] + add_index 'invoices', ['delivery_id'] - add_index "order_articles", ["order_id"] + add_index 'order_articles', ['order_id'] - add_index "order_comments", ["order_id"] + add_index 'order_comments', ['order_id'] - add_index "orders", ["state"] + add_index 'orders', ['state'] - add_index "stock_changes", ["delivery_id"] - add_index "stock_changes", ["stock_article_id"] - add_index "stock_changes", ["stock_taking_id"] + add_index 'stock_changes', ['delivery_id'] + add_index 'stock_changes', ['stock_article_id'] + add_index 'stock_changes', ['stock_taking_id'] - add_index "tasks", ["workgroup_id"] + add_index 'tasks', ['workgroup_id'] end - def self.down - end + def self.down; end end diff --git a/db/migrate/20110507184920_add_duration_to_tasks.rb b/db/migrate/20110507184920_add_duration_to_tasks.rb index 33a11494..86347508 100644 --- a/db/migrate/20110507184920_add_duration_to_tasks.rb +++ b/db/migrate/20110507184920_add_duration_to_tasks.rb @@ -1,6 +1,6 @@ class AddDurationToTasks < ActiveRecord::Migration[4.2] def self.up - add_column :tasks, :duration, :integer, :default => 1 + add_column :tasks, :duration, :integer, default: 1 end def self.down diff --git a/db/migrate/20110507192928_add_task_duration_to_workgroups.rb b/db/migrate/20110507192928_add_task_duration_to_workgroups.rb index c5b4844b..fd703d17 100644 --- a/db/migrate/20110507192928_add_task_duration_to_workgroups.rb +++ b/db/migrate/20110507192928_add_task_duration_to_workgroups.rb @@ -1,6 +1,6 @@ class AddTaskDurationToWorkgroups < ActiveRecord::Migration[4.2] def self.up - add_column :groups, :task_duration, :integer, :default => 1 + add_column :groups, :task_duration, :integer, default: 1 end def self.down diff --git a/db/migrate/20120622094337_add_next_weekly_tasks_number_to_workgroups.rb b/db/migrate/20120622094337_add_next_weekly_tasks_number_to_workgroups.rb index b8ac8c81..eeca92b3 100644 --- a/db/migrate/20120622094337_add_next_weekly_tasks_number_to_workgroups.rb +++ b/db/migrate/20120622094337_add_next_weekly_tasks_number_to_workgroups.rb @@ -1,6 +1,6 @@ class AddNextWeeklyTasksNumberToWorkgroups < ActiveRecord::Migration[4.2] def self.up - add_column :groups, :next_weekly_tasks_number, :integer, :default => 8 + add_column :groups, :next_weekly_tasks_number, :integer, default: 8 end def self.down diff --git a/db/migrate/20130622095040_move_weekly_tasks.rb b/db/migrate/20130622095040_move_weekly_tasks.rb index b780f3e9..f0bea5e2 100644 --- a/db/migrate/20130622095040_move_weekly_tasks.rb +++ b/db/migrate/20130622095040_move_weekly_tasks.rb @@ -15,21 +15,21 @@ def up def down PeriodicTaskGroup.all.each do |task_group| - unless task_group.tasks.empty? - task = task_group.tasks.first - workgroup = task.workgroup - puts "Writing task data of group #{task_group.id} to workgroup #{workgroup.name}" - workgroup_attributes = { - weekly_task: true, - weekday: task.due_date.days_to_week_start(:sunday), - task_name: task.name, - task_description: task.description, - task_required_users: task.required_users, - task_duration: task.duration - } - workgroup.update_attributes workgroup_attributes - task_group.tasks.update_all weekly: true - end + next if task_group.tasks.empty? + + task = task_group.tasks.first + workgroup = task.workgroup + puts "Writing task data of group #{task_group.id} to workgroup #{workgroup.name}" + workgroup_attributes = { + weekly_task: true, + weekday: task.due_date.days_to_week_start(:sunday), + task_name: task.name, + task_description: task.description, + task_required_users: task.required_users, + task_duration: task.duration + } + workgroup.update_attributes workgroup_attributes + task_group.tasks.update_all weekly: true end end diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb index da57126a..52edbad4 100644 --- a/db/migrate/20130702113610_update_group_order_totals.rb +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -1,18 +1,17 @@ class UpdateGroupOrderTotals < ActiveRecord::Migration[4.2] def self.up say "If you have ever modified an order after it was settled, the group_order's " + - "price may be calculated incorrectly. This can take a lot of time on a " + - "large database." + 'price may be calculated incorrectly. This can take a lot of time on a ' + + 'large database.' - say "If you do want to update the ordergroup totals, open the rails console " + - "(by running `rails c`), and enter:" + say 'If you do want to update the ordergroup totals, open the rails console ' + + '(by running `rails c`), and enter:' - say "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }", subitem: true + say 'GroupOrder.all.each { |go| go.order.closed? and go.update_price! }', subitem: true - say "You may want to check first that no undesired accounting issues are introduced. " + - "It may be wise to discuss this with those responsible for the ordering finances." + say 'You may want to check first that no undesired accounting issues are introduced. ' + + 'It may be wise to discuss this with those responsible for the ordering finances.' end - def self.down - end + def self.down; end end diff --git a/db/migrate/20130718183100_create_settings.rb b/db/migrate/20130718183100_create_settings.rb index 9928a9b3..90639d71 100644 --- a/db/migrate/20130718183100_create_settings.rb +++ b/db/migrate/20130718183100_create_settings.rb @@ -8,7 +8,7 @@ def self.up t.timestamps end - add_index :settings, [:thing_type, :thing_id, :var], unique: true + add_index :settings, %i[thing_type thing_id var], unique: true end def self.down diff --git a/db/migrate/20130718183101_migrate_user_settings.rb b/db/migrate/20130718183101_migrate_user_settings.rb index cd173e45..2d0e3c56 100644 --- a/db/migrate/20130718183101_migrate_user_settings.rb +++ b/db/migrate/20130718183101_migrate_user_settings.rb @@ -16,7 +16,7 @@ def up begin user = type.constantize.find(id) rescue ActiveRecord::RecordNotFound - Rails.logger.debug "Can't find configurable object with type: #{type.inspect}, id: #{id.inspect}" + Rails.logger.debug { "Can't find configurable object with type: #{type.inspect}, id: #{id.inspect}" } next end @@ -27,7 +27,7 @@ def up # prepare value value = YAML.load(old_setting.value) - value = value.nil? ? false : value + value = false if value.nil? # set the settings_attributes (thanks to settings.merge! we can set them one by one) user.settings_attributes = { @@ -46,8 +46,7 @@ def up drop_table :configurable_settings end - def down - end + def down; end end # this is the base class of all configurable settings diff --git a/db/migrate/20130920201529_allow_missing_nick.rb b/db/migrate/20130920201529_allow_missing_nick.rb index ed818860..fcf1d8c8 100644 --- a/db/migrate/20130920201529_allow_missing_nick.rb +++ b/db/migrate/20130920201529_allow_missing_nick.rb @@ -1,9 +1,9 @@ class AllowMissingNick < ActiveRecord::Migration[4.2] def self.up - change_column :users, :nick, :string, :default => nil, :null => true + change_column :users, :nick, :string, default: nil, null: true end def self.down - change_column :users, :nick, :string, :default => "", :null => false + change_column :users, :nick, :string, default: '', null: false end end diff --git a/db/migrate/20140102170431_add_result_computed_to_group_order_articles.rb b/db/migrate/20140102170431_add_result_computed_to_group_order_articles.rb index dd9fc407..0bb885d9 100644 --- a/db/migrate/20140102170431_add_result_computed_to_group_order_articles.rb +++ b/db/migrate/20140102170431_add_result_computed_to_group_order_articles.rb @@ -1,6 +1,6 @@ class AddResultComputedToGroupOrderArticles < ActiveRecord::Migration[4.2] def change add_column :group_order_articles, :result_computed, - :decimal, :precision => 8, :scale => 3 + :decimal, precision: 8, scale: 3 end end diff --git a/db/migrate/20140318173000_delete_empty_group_order_articles.rb b/db/migrate/20140318173000_delete_empty_group_order_articles.rb index 1e053c3c..c5b396ed 100644 --- a/db/migrate/20140318173000_delete_empty_group_order_articles.rb +++ b/db/migrate/20140318173000_delete_empty_group_order_articles.rb @@ -4,6 +4,5 @@ def up GroupOrderArticle.where(quantity: 0, tolerance: 0, result: [0, nil], result_computed: [0, nil]).delete_all end - def down - end + def down; end end diff --git a/db/migrate/20140921104907_remove_stale_memberships.rb b/db/migrate/20140921104907_remove_stale_memberships.rb index de5b719b..26b6c834 100644 --- a/db/migrate/20140921104907_remove_stale_memberships.rb +++ b/db/migrate/20140921104907_remove_stale_memberships.rb @@ -1,5 +1,5 @@ class RemoveStaleMemberships < ActiveRecord::Migration[4.2] def up - Membership.where("group_id NOT IN (?)", Group.ids).delete_all + Membership.where.not(group_id: Group.ids).delete_all end end diff --git a/db/migrate/20160217194036_add_role_invoices_to_group.rb b/db/migrate/20160217194036_add_role_invoices_to_group.rb index 6946fe05..5a86f425 100644 --- a/db/migrate/20160217194036_add_role_invoices_to_group.rb +++ b/db/migrate/20160217194036_add_role_invoices_to_group.rb @@ -1,5 +1,5 @@ class AddRoleInvoicesToGroup < ActiveRecord::Migration[4.2] def change - add_column :groups, :role_invoices, :boolean, :default => false, :null => false + add_column :groups, :role_invoices, :boolean, default: false, null: false end end diff --git a/db/migrate/20160218151041_add_attachment_to_invoice.rb b/db/migrate/20160218151041_add_attachment_to_invoice.rb index 58bac66d..1767905c 100644 --- a/db/migrate/20160218151041_add_attachment_to_invoice.rb +++ b/db/migrate/20160218151041_add_attachment_to_invoice.rb @@ -1,6 +1,6 @@ class AddAttachmentToInvoice < ActiveRecord::Migration[4.2] def change add_column :invoices, :attachment_mime, :string - add_column :invoices, :attachment_data, :binary, :limit => 8.megabyte + add_column :invoices, :attachment_data, :binary, limit: 8.megabyte end end diff --git a/db/migrate/20160219123220_create_financial_transaction_class_and_types.rb b/db/migrate/20160219123220_create_financial_transaction_class_and_types.rb index 5fcf318b..3c05035d 100644 --- a/db/migrate/20160219123220_create_financial_transaction_class_and_types.rb +++ b/db/migrate/20160219123220_create_financial_transaction_class_and_types.rb @@ -1,12 +1,12 @@ class CreateFinancialTransactionClassAndTypes < ActiveRecord::Migration[4.2] def change create_table :financial_transaction_classes do |t| - t.string :name, :null => false + t.string :name, null: false end create_table :financial_transaction_types do |t| - t.string :name, :null => false - t.references :financial_transaction_class, :null => false + t.string :name, null: false + t.references :financial_transaction_class, null: false end change_table :financial_transactions do |t| @@ -17,7 +17,7 @@ def change dir.up do execute "INSERT INTO financial_transaction_classes (id, name) VALUES (1, 'Standard')" execute "INSERT INTO financial_transaction_types (id, name, financial_transaction_class_id) VALUES (1, 'Foodsoft', 1)" - execute "UPDATE financial_transactions SET financial_transaction_type_id = 1" + execute 'UPDATE financial_transactions SET financial_transaction_type_id = 1' end end diff --git a/db/migrate/20160224201529_allow_stock_group_order.rb b/db/migrate/20160224201529_allow_stock_group_order.rb index a77879e3..6c9197f0 100644 --- a/db/migrate/20160224201529_allow_stock_group_order.rb +++ b/db/migrate/20160224201529_allow_stock_group_order.rb @@ -1,9 +1,9 @@ class AllowStockGroupOrder < ActiveRecord::Migration[4.2] def self.up - change_column :group_orders, :ordergroup_id, :integer, :default => nil, :null => true + change_column :group_orders, :ordergroup_id, :integer, default: nil, null: true end def self.down - change_column :group_orders, :ordergroup_id, :integer, :default => 0, :null => false + change_column :group_orders, :ordergroup_id, :integer, default: 0, null: false end end diff --git a/db/migrate/20160226000000_add_email_to_message.foodsoft_messages_engine.rb b/db/migrate/20160226000000_add_email_to_message.foodsoft_messages_engine.rb index 95b35273..ceeafa15 100644 --- a/db/migrate/20160226000000_add_email_to_message.foodsoft_messages_engine.rb +++ b/db/migrate/20160226000000_add_email_to_message.foodsoft_messages_engine.rb @@ -2,6 +2,6 @@ class AddEmailToMessage < ActiveRecord::Migration[4.2] def change add_column :messages, :salt, :string - add_column :messages, :received_email, :binary, :limit => 1.megabyte + add_column :messages, :received_email, :binary, limit: 1.megabyte end end diff --git a/db/migrate/20170801000000_create_mail_delivery_status.rb b/db/migrate/20170801000000_create_mail_delivery_status.rb index 2fd40674..69fa1b75 100644 --- a/db/migrate/20170801000000_create_mail_delivery_status.rb +++ b/db/migrate/20170801000000_create_mail_delivery_status.rb @@ -2,8 +2,8 @@ class CreateMailDeliveryStatus < ActiveRecord::Migration[4.2] def change create_table :mail_delivery_status do |t| t.datetime :created_at - t.string :email, :null => false - t.string :message, :null => false + t.string :email, null: false + t.string :message, null: false t.string :attachment_mime t.binary :attachment_data, limit: 16.megabyte diff --git a/db/migrate/20171002000000_create_financial_links.rb b/db/migrate/20171002000000_create_financial_links.rb index 5f42ec2e..c906869b 100644 --- a/db/migrate/20171002000000_create_financial_links.rb +++ b/db/migrate/20171002000000_create_financial_links.rb @@ -4,7 +4,9 @@ def change t.text :note end - add_column :financial_transactions, :financial_link_id, :integer, index: true - add_column :invoices, :financial_link_id, :integer, index: true + add_column :financial_transactions, :financial_link_id, :integer + add_index :financial_transactions, :financial_link_id + add_column :invoices, :financial_link_id, :integer + add_index :invoices, :financial_link_id end end diff --git a/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb b/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb index 990e75f0..120b7eef 100644 --- a/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb +++ b/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb @@ -24,14 +24,14 @@ def change t.references :ordergroup t.text :note t.timestamps - t.index [:poll_id, :user_id, :ordergroup_id], unique: true + t.index %i[poll_id user_id ordergroup_id], unique: true end create_table :poll_choices do |t| t.references :poll_vote, null: false t.integer :choice, null: false t.integer :value, null: false - t.index [:poll_vote_id, :choice], unique: true + t.index %i[poll_vote_id choice], unique: true end end end diff --git a/db/migrate/20181120000000_increase_choices_size.foodsoft_polls_engine.rb b/db/migrate/20181120000000_increase_choices_size.foodsoft_polls_engine.rb index d809e3ea..621863dd 100644 --- a/db/migrate/20181120000000_increase_choices_size.foodsoft_polls_engine.rb +++ b/db/migrate/20181120000000_increase_choices_size.foodsoft_polls_engine.rb @@ -1,5 +1,5 @@ class IncreaseChoicesSize < ActiveRecord::Migration[4.2] def up - change_column :polls, :choices, :text, limit: 65535 + change_column :polls, :choices, :text, limit: 65_535 end end diff --git a/db/migrate/20181201000000_create_printer_jobs.foodsoft_printer_engine.rb b/db/migrate/20181201000000_create_printer_jobs.foodsoft_printer_engine.rb index ee7665e4..36d175c5 100644 --- a/db/migrate/20181201000000_create_printer_jobs.foodsoft_printer_engine.rb +++ b/db/migrate/20181201000000_create_printer_jobs.foodsoft_printer_engine.rb @@ -15,6 +15,6 @@ def change t.text :message end - add_index :printer_job_updates, [:printer_job_id, :created_at] + add_index :printer_job_updates, %i[printer_job_id created_at] end end diff --git a/db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb b/db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb index e931f748..b1d0d51c 100644 --- a/db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb +++ b/db/migrate/20181201000100_create_message_recipients.foodsoft_messages.rb @@ -14,7 +14,7 @@ def up t.datetime :read_at end - add_index :message_recipients, [:user_id, :read_at] + add_index :message_recipients, %i[user_id read_at] Message.all.each do |m| recipients = YAML.load(m.recipients_ids).map do |r| diff --git a/db/migrate/20190101000000_create_active_storage_tables.active_storage.rb b/db/migrate/20190101000000_create_active_storage_tables.active_storage.rb index 3739c2e8..650274b4 100644 --- a/db/migrate/20190101000000_create_active_storage_tables.active_storage.rb +++ b/db/migrate/20190101000000_create_active_storage_tables.active_storage.rb @@ -20,7 +20,7 @@ def change t.datetime :created_at, null: false - t.index [:record_type, :record_id, :name, :blob_id], name: "index_active_storage_attachments_uniqueness", unique: true + t.index %i[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', unique: true t.foreign_key :active_storage_blobs, column: :blob_id end end diff --git a/db/migrate/20210205090257_introduce_received_state_in_orders.rb b/db/migrate/20210205090257_introduce_received_state_in_orders.rb index ffeff588..ca7ce999 100644 --- a/db/migrate/20210205090257_introduce_received_state_in_orders.rb +++ b/db/migrate/20210205090257_introduce_received_state_in_orders.rb @@ -1,7 +1,7 @@ class IntroduceReceivedStateInOrders < ActiveRecord::Migration[5.2] def up Order.where(state: 'finished').each do |order| - order.update_attribute(:state, 'received') if order.order_articles.where('units_received IS NOT NULL').any? + order.update_attribute(:state, 'received') if order.order_articles.where.not(units_received: nil).any? end end diff --git a/db/migrate/20221026102300_alter_articles_add_versioning.rb b/db/migrate/20221026102300_alter_articles_add_versioning.rb new file mode 100644 index 00000000..7914c8cd --- /dev/null +++ b/db/migrate/20221026102300_alter_articles_add_versioning.rb @@ -0,0 +1,217 @@ +class AlterArticlesAddVersioning < ActiveRecord::Migration[5.2] + def up + rename_table :article_prices, :article_versions + rename_column :order_articles, :article_price_id, :article_version_id + + change_table :article_versions do |t| + t.string :name, default: '', null: false + t.integer :article_category_id, default: 0, null: false + t.string :unit, default: '' + t.string :note + t.boolean :availability, default: true, null: false + t.string :manufacturer + t.string :origin + t.string :order_number + t.datetime :updated_at + end + + # copy all article fields into article_versions + articles = select_all('SELECT article_versions.id AS article_version_id, articles.* FROM article_versions JOIN articles ON articles.id = article_versions.article_id') + articles.each do |article| + update(%( + UPDATE article_versions SET + name = #{quote article['name']}, + article_category_id = #{quote article['article_category_id']}, + unit = #{quote article['unit']}, + note = #{quote article['note']}, + availability = #{quote article['availability']}, + manufacturer = #{quote article['manufacturer']}, + origin = #{quote article['origin']}, + order_number = #{quote article['order_number']}, + updated_at = #{quote article['updated_at']} + WHERE id = #{quote article['article_version_id']} + )) + end + + remove_index :articles, %i[name supplier_id] + + # drop article columns (now superfluous as they exist in article_versions): + change_table :articles do |t| + t.remove :name + t.remove :article_category_id + t.remove :unit + t.remove :note + t.remove :availability + t.remove :manufacturer + t.remove :origin + t.remove :order_number + t.remove :updated_at + t.remove :price + t.remove :tax + t.remove :deposit + t.remove :unit_quantity + end + + # remove order_articles' reference to articles (reference now always goes through article_versions): + articles = select_all(%( + SELECT articles.id AS article_id, article_versions.id AS article_version_id + FROM articles + JOIN article_versions ON article_versions.article_id = articles.id + JOIN order_articles ON order_articles.article_id = articles.id + WHERE order_articles.article_version_id IS NULL + GROUP BY article_versions.article_id + ORDER BY article_versions.created_at DESC + )) + + articles.each do |article| + update(%( + UPDATE order_articles + SET article_version_id = #{quote article['article_version_id']} + WHERE order_articles.article_version_id IS NULL + AND order_articles.article_id = #{quote article['article_id']} + )) + end + + # Remove orphaned order articles (db inconsistencies due to lack of foreign key constraints): + delete('DELETE FROM order_articles WHERE order_articles.article_version_id IS NULL') + + # De-duplicate article version (db inconsistencies due to lack of unique key for created_at and article_id): + duplicate_article_versions = select_all(%{ + SELECT article_id, created_at + FROM article_versions + GROUP BY article_id, created_at + HAVING COUNT(*) > 1 + }) + + duplicate_article_versions.each do |duplicate_article_version| + article_versions = select_all(%( + SELECT id + FROM article_versions + WHERE article_id = #{quote duplicate_article_version['article_id']} + AND created_at = #{quote duplicate_article_version['created_at']} + )) + + latest_version = article_versions.last + article_versions[0..-2].each do |obsolete_version| + update("UPDATE order_articles SET article_version_id = #{quote latest_version['id']} WHERE article_version_id = #{quote obsolete_version['id']}") + delete("DELETE FROM article_versions WHERE id = #{quote obsolete_version['id']}") + end + end + + remove_index :order_articles, %i[order_id article_id] + remove_column :order_articles, :article_id + change_column_null :order_articles, :article_version_id, false + add_index :order_articles, %i[order_id article_version_id], unique: true + add_index :order_articles, :article_version_id + remove_index :article_versions, :article_id + add_index :article_versions, %i[article_id created_at], unique: true + add_index :article_versions, [:article_category_id] + end + + def down + rename_table :article_versions, :article_prices + rename_column :order_articles, :article_version_id, :article_price_id + + remove_index :order_articles, %i[order_id article_price_id] + remove_index :order_articles, :article_price_id + remove_index :article_prices, %i[article_id created_at] + remove_index :article_prices, [:article_category_id] + + add_column :order_articles, :article_id, :integer + change_column_null :order_articles, :article_price_id, true + + change_table :articles do |t| + t.string :name, default: '', null: false + t.integer :article_category_id, default: 0, null: false + t.string :unit, default: '' + t.string :note + t.boolean :availability, default: true, null: false + t.string :manufacturer + t.string :origin + t.string :order_number + t.datetime :updated_at + t.decimal :price, precision: 8, scale: 2 + t.float :tax + t.decimal :deposit, precision: 8, scale: 2, default: '0.0' + t.integer :unit_quantity, null: false, default: 0 + end + + article_prices = select_all(%{ + SELECT article_prices.* + FROM article_prices + JOIN ( + SELECT article_id, MAX(created_at) AS max_created_at + FROM article_prices + GROUP BY article_id + ) AS latest_article_prices + ON latest_article_prices.article_id = article_prices.article_id + AND latest_article_prices.max_created_at = article_prices.created_at + }) + article_prices.each do |article_price| + update(%( + UPDATE articles SET + name = #{quote article_price['name']}, + article_category_id = #{quote article_price['article_category_id']}, + unit = #{quote article_price['unit']}, + note = #{quote article_price['note']}, + availability = #{quote article_price['availability']}, + manufacturer = #{quote article_price['manufacturer']}, + origin = #{quote article_price['origin']}, + order_number = #{quote article_price['order_number']}, + updated_at = #{quote article_price['updated_at']}, + type = #{quote article_price['type']}, + price = #{quote article_price['price']}, + tax = #{quote article_price['tax']}, + deposit = #{quote article_price['deposit']}, + unit_quantity = #{quote article_price['unit_quantity']} + WHERE id = #{quote article_price['article_id']} + )) + end + + order_articles = select_all(%( + SELECT order_articles.id, article_prices.article_id + FROM order_articles + JOIN article_prices ON article_prices.id = order_articles.article_price_id + )) + + order_articles.each do |order_article| + update(%( + UPDATE order_articles + SET article_id = #{quote order_article['article_id']} + WHERE id = #{quote order_article['id']} + )) + end + + update(%{ + UPDATE order_articles + SET article_price_id = NULL + WHERE order_id IN (SELECT id FROM orders WHERE state = #{quote 'open'}) + }) + + change_table :article_prices do |t| + t.remove :name + t.remove :article_category_id + t.remove :unit + t.remove :note + t.remove :availability + t.remove :manufacturer + t.remove :origin + t.remove :order_number + t.remove :updated_at + end + + change_column_default :articles, :unit_quantity, nil + change_column_null :order_articles, :article_id, false + add_index :order_articles, %i[order_id article_id], unique: true + add_index :article_prices, :article_id + add_index :articles, %i[name supplier_id] + end + + protected + + # We cannot use quote out of context (as it wouldn't relate to the current DB syntax), + # but using Article's db connection by default should be fine + def quote(value) + Article.connection.quote(value) + end +end diff --git a/db/migrate/20221026102301_alter_articles_add_more_unit_logic.rb b/db/migrate/20221026102301_alter_articles_add_more_unit_logic.rb new file mode 100644 index 00000000..35740693 --- /dev/null +++ b/db/migrate/20221026102301_alter_articles_add_more_unit_logic.rb @@ -0,0 +1,106 @@ +class AlterArticlesAddMoreUnitLogic < ActiveRecord::Migration[5.2] + def up + change_table :article_versions do |t| + t.column :supplier_order_unit, :string, length: 3 + t.column :price_unit, :string, length: 3 + t.column :billing_unit, :string, length: 3 + t.column :group_order_unit, :string, length: 3 + t.column :group_order_granularity, :decimal, precision: 8, scale: 3, null: false, default: 1 + t.column :minimum_order_quantity, :float + t.change :unit, :string, null: true, default: nil + end + + create_table :article_unit_ratios do |t| + t.references :article_version, null: false + + t.column :sort, :integer, null: false, index: true + t.column :quantity, :decimal, precision: 8, scale: 3, null: false + t.column :unit, :string, length: 3 + end + + article_versions = select_all('SELECT id, unit, unit_quantity, price FROM article_versions WHERE unit_quantity > 1 AND NOT unit IS NULL') + article_versions.each do |article_version| + insert(%{ + INSERT INTO article_unit_ratios (article_version_id, sort, quantity, unit) + VALUES ( + #{quote article_version['id']}, + #{quote 1}, + #{quote article_version['unit_quantity']}, + #{quote 'XPP'} + ) + }) + + compound_unit = "#{article_version['unit_quantity']}x#{article_version['unit']}" + update(%( + UPDATE article_versions + SET unit = #{quote compound_unit}, + group_order_granularity = #{quote 1}, + group_order_unit = #{quote 'XPP'}, + price = #{quote article_version['price'].to_f * article_version['unit_quantity']}, + price_unit = #{quote 'XPP'}, + billing_unit = #{quote 'XPP'} + WHERE article_versions.id = #{quote article_version['id']} + )) + end + + change_table :article_versions do |t| + t.remove :unit_quantity + end + + change_table :order_articles do |t| + t.change :quantity, :decimal, precision: 8, scale: 3, null: false + t.change :tolerance, :decimal, precision: 8, scale: 3, null: false + t.change :units_to_order, :decimal, precision: 8, scale: 3, null: false + end + + change_table :group_order_articles do |t| + t.change :quantity, :decimal, precision: 8, scale: 3, null: false + t.change :tolerance, :decimal, precision: 8, scale: 3, null: false + end + + change_table :group_order_article_quantities do |t| + t.change :quantity, :decimal, precision: 8, scale: 3, null: false + t.change :tolerance, :decimal, precision: 8, scale: 3, null: false + end + end + + def down + change_table :article_versions do |t| + t.remove :supplier_order_unit + t.remove :price_unit + t.remove :billing_unit + t.remove :group_order_unit + t.remove :group_order_granularity + t.remove :minimum_order_quantity + t.column :unit_quantity, :integer, null: false + t.change :unit, :string, null: true, default: '' + end + + article_unit_ratios = select_all('SELECT article_version_id, quantity FROM article_unit_ratios WHERE sort=1') + article_unit_ratios.each do |article_unit_ratio| + update(%( + UPDATE article_versions + SET unit_quantity = #{quote article_unit_ratio['quantity']} + WHERE id = #{quote article_unit_ratio['article_version_id']} + )) + end + + drop_table :article_unit_ratios + + change_table :order_articles do |t| + t.change :quantity, :integer, null: false + t.change :tolerance, :integer, null: false + t.change :units_to_order, :integer, null: false + end + + change_table :group_order_articles do |t| + t.change :quantity, :integer, null: false + t.change :tolerance, :integer, null: false + end + + change_table :group_order_article_quantities do |t| + t.change :quantity, :integer, null: false + t.change :tolerance, :integer, null: false + end + end +end diff --git a/db/migrate/20230414122017_alter_suppliers_sharing_fields.rb b/db/migrate/20230414122017_alter_suppliers_sharing_fields.rb new file mode 100644 index 00000000..5d66df18 --- /dev/null +++ b/db/migrate/20230414122017_alter_suppliers_sharing_fields.rb @@ -0,0 +1,19 @@ +class AlterSuppliersSharingFields < ActiveRecord::Migration[5.2] + def up + change_table :suppliers do |t| + t.remove :shared_supplier_id + t.column :supplier_remote_source, :string + t.column :external_uuid, :string + end + + add_index :suppliers, :external_uuid, unique: true + end + + def down + change_table :suppliers do |t| + t.column :shared_supplier_id, :integer + t.remove :supplier_remote_source + t.remove :external_uuid + end + end +end diff --git a/db/migrate/20231104141204_create_article_units.rb b/db/migrate/20231104141204_create_article_units.rb new file mode 100644 index 00000000..93402f9f --- /dev/null +++ b/db/migrate/20231104141204_create_article_units.rb @@ -0,0 +1,29 @@ +class CreateArticleUnits < ActiveRecord::Migration[5.2] + def up + create_table :article_units, id: false do |t| + t.string :unit, length: 3, null: false + + t.timestamps + end + + add_index :article_units, :unit, unique: true + + # TODO: modify list and make it scan current article units to determine, if metric, imperial or both unit types should be added + # (see see https://github.com/foodcoopsat/foodsoft_hackathon/issues/35): + unit_codes = %w[GRM HGM KGM LTR MLT PTN STC XPP XCR XBO XBH XGR XPK XSA XPU XPT] + unit_codes.each do |unit_code| + insert(%{ + INSERT INTO article_units (unit, created_at, updated_at) + VALUES ( + #{quote unit_code}, + NOW(), + NOW() + ) + }) + end + end + + def down + drop_table :article_units + end +end diff --git a/db/migrate/20231210181807_add_unit_migration_completed_to_suppliers.rb b/db/migrate/20231210181807_add_unit_migration_completed_to_suppliers.rb new file mode 100644 index 00000000..497e0077 --- /dev/null +++ b/db/migrate/20231210181807_add_unit_migration_completed_to_suppliers.rb @@ -0,0 +1,5 @@ +class AddUnitMigrationCompletedToSuppliers < ActiveRecord::Migration[5.2] + def change + add_column :suppliers, :unit_migration_completed, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index ce812b3f..b403900b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,538 +10,558 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_02_05_090257) do - - create_table "active_storage_attachments", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.bigint "record_id", null: false - t.bigint "blob_id", null: false - t.datetime "created_at", null: false - t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" - t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true - end - - create_table "active_storage_blobs", id: :integer, force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.bigint "byte_size", null: false - t.string "checksum", null: false - t.datetime "created_at", null: false - t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true - end - - create_table "article_categories", id: :integer, force: :cascade do |t| - t.string "name", default: "", null: false - t.string "description" - t.index ["name"], name: "index_article_categories_on_name", unique: true - end - - create_table "article_prices", id: :integer, force: :cascade do |t| - t.integer "article_id", null: false - t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false - t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false - t.decimal "deposit", precision: 8, scale: 2, default: "0.0", null: false - t.integer "unit_quantity" - t.datetime "created_at" - t.index ["article_id"], name: "index_article_prices_on_article_id" - end - - create_table "articles", id: :integer, force: :cascade do |t| - t.string "name", default: "", null: false - t.integer "supplier_id", default: 0, null: false - t.integer "article_category_id", default: 0, null: false - t.string "unit", default: "", null: false - t.string "note" - t.boolean "availability", default: true, null: false - t.string "manufacturer" - t.string "origin" - t.datetime "shared_updated_on" - t.decimal "price", precision: 8, scale: 2 - t.float "tax" - t.decimal "deposit", precision: 8, scale: 2, default: "0.0" - t.integer "unit_quantity", default: 1, null: false - t.string "order_number" - t.datetime "created_at" - t.datetime "updated_at" - t.datetime "deleted_at" - t.string "type" - t.integer "quantity", default: 0 - t.index ["article_category_id"], name: "index_articles_on_article_category_id" - t.index ["name", "supplier_id"], name: "index_articles_on_name_and_supplier_id" - t.index ["supplier_id"], name: "index_articles_on_supplier_id" - t.index ["type"], name: "index_articles_on_type" - end - - create_table "assignments", id: :integer, force: :cascade do |t| - t.integer "user_id", default: 0, null: false - t.integer "task_id", default: 0, null: false - t.boolean "accepted", default: false - t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true - end - - create_table "bank_accounts", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.string "iban" - t.string "description" - t.decimal "balance", precision: 12, scale: 2, default: "0.0", null: false - t.datetime "last_import" - t.string "import_continuation_point" - t.integer "bank_gateway_id" - end - - create_table "bank_gateways", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.string "url", null: false - t.string "authorization" - t.integer "unattended_user_id" - end - - create_table "bank_transactions", id: :integer, force: :cascade do |t| - t.integer "bank_account_id", null: false - t.string "external_id" - t.date "date" - t.decimal "amount", precision: 8, scale: 2, null: false - t.string "iban" - t.string "reference" - t.text "text" - t.text "receipt" - t.binary "image", limit: 16777215 - t.integer "financial_link_id" - t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id" - end - - create_table "documents", id: :integer, force: :cascade do |t| - t.string "name" - t.string "mime" - t.binary "data", limit: 4294967295 - t.integer "created_by_user_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "parent_id" - t.index ["parent_id"], name: "index_documents_on_parent_id" - end - - create_table "financial_links", id: :integer, force: :cascade do |t| - t.text "note" - end - - create_table "financial_transaction_classes", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.boolean "ignore_for_account_balance", default: false, null: false - end - - create_table "financial_transaction_types", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.integer "financial_transaction_class_id", null: false - t.string "name_short" - t.integer "bank_account_id" - t.index ["name_short"], name: "index_financial_transaction_types_on_name_short" - end - - create_table "financial_transactions", id: :integer, force: :cascade do |t| - t.integer "ordergroup_id" - t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false - t.text "note", null: false - t.integer "user_id", default: 0, null: false - t.datetime "created_on", null: false - t.integer "financial_transaction_type_id", null: false - t.integer "financial_link_id" - t.integer "reverts_id" - t.integer "group_order_id" - t.index ["ordergroup_id"], name: "index_financial_transactions_on_ordergroup_id" - t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true - end - - create_table "group_order_article_quantities", id: :integer, force: :cascade do |t| - t.integer "group_order_article_id", default: 0, null: false - t.integer "quantity", default: 0 - t.integer "tolerance", default: 0 - t.datetime "created_on", null: false - t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id" - end - - create_table "group_order_articles", id: :integer, force: :cascade do |t| - t.integer "group_order_id", default: 0, null: false - t.integer "order_article_id", default: 0, null: false - t.integer "quantity", default: 0, null: false - t.integer "tolerance", default: 0, null: false - t.datetime "updated_on", null: false - t.decimal "result", precision: 8, scale: 3 - t.decimal "result_computed", precision: 8, scale: 3 - t.index ["group_order_id", "order_article_id"], name: "goa_index", unique: true - t.index ["group_order_id"], name: "index_group_order_articles_on_group_order_id" - t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id" - end - - create_table "group_orders", id: :integer, force: :cascade do |t| - t.integer "ordergroup_id" - t.integer "order_id", default: 0, null: false - t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false - t.integer "lock_version", default: 0, null: false - t.datetime "updated_on", null: false - t.integer "updated_by_user_id" - t.decimal "transport", precision: 8, scale: 2 - t.index ["order_id"], name: "index_group_orders_on_order_id" - t.index ["ordergroup_id", "order_id"], name: "index_group_orders_on_ordergroup_id_and_order_id", unique: true - t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id" - end - - create_table "groups", id: :integer, force: :cascade do |t| - t.string "type", default: "", null: false - t.string "name", default: "", null: false - t.string "description" - t.decimal "account_balance", precision: 12, scale: 2, default: "0.0", null: false - t.datetime "created_on", null: false - t.boolean "role_admin", default: false, null: false - t.boolean "role_suppliers", default: false, null: false - t.boolean "role_article_meta", default: false, null: false - t.boolean "role_finance", default: false, null: false - t.boolean "role_orders", default: false, null: false - t.datetime "deleted_at" - t.string "contact_person" - t.string "contact_phone" - t.string "contact_address" - t.text "stats" - t.integer "next_weekly_tasks_number", default: 8 - t.boolean "ignore_apple_restriction", default: false - t.date "break_start" - t.date "break_end" - t.boolean "role_invoices", default: false, null: false - t.boolean "role_pickups", default: false, null: false - t.index ["name"], name: "index_groups_on_name", unique: true - end - - create_table "invites", id: :integer, force: :cascade do |t| - t.string "token", default: "", null: false - t.datetime "expires_at", null: false - t.integer "group_id", default: 0, null: false - t.integer "user_id", default: 0, null: false - t.string "email", default: "", null: false - t.index ["token"], name: "index_invites_on_token" - end - - create_table "invoices", id: :integer, force: :cascade do |t| - t.integer "supplier_id" - t.string "number" - t.date "date" - t.date "paid_on" - t.text "note" - t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false - t.decimal "deposit", precision: 8, scale: 2, default: "0.0", null: false - t.decimal "deposit_credit", precision: 8, scale: 2, default: "0.0", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "created_by_user_id" - t.string "attachment_mime" - t.binary "attachment_data", limit: 16777215 - t.integer "financial_link_id" - t.index ["supplier_id"], name: "index_invoices_on_supplier_id" - end - - create_table "links", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.string "url", null: false - t.integer "workgroup_id" - t.boolean "indirect", default: false, null: false - t.string "authorization" - end - - create_table "mail_delivery_status", id: :integer, force: :cascade do |t| - t.datetime "created_at" - t.string "email", null: false - t.string "message", null: false - t.string "attachment_mime" - t.binary "attachment_data", limit: 4294967295 - t.index ["email"], name: "index_mail_delivery_status_on_email" - end - - create_table "memberships", id: :integer, force: :cascade do |t| - t.integer "group_id", default: 0, null: false - t.integer "user_id", default: 0, null: false - t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true - end - - create_table "message_recipients", id: :integer, force: :cascade do |t| - t.integer "message_id", null: false - t.integer "user_id", null: false - t.integer "email_state", default: 0, null: false - t.datetime "read_at" - t.index ["message_id"], name: "index_message_recipients_on_message_id" - t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at" - end - - create_table "messages", id: :integer, force: :cascade do |t| - t.integer "sender_id" - t.string "subject", null: false - t.text "body" - t.boolean "private", default: false - t.datetime "created_at" - t.integer "reply_to" - t.integer "group_id" - t.string "salt" - t.binary "received_email", limit: 16777215 - end - - create_table "oauth_access_grants", id: :integer, force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false - t.datetime "revoked_at" - t.string "scopes" - t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true - end - - create_table "oauth_access_tokens", id: :integer, force: :cascade do |t| - t.integer "resource_owner_id" - t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" - t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" - t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true - t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id" - t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true - end - - create_table "oauth_applications", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "confidential", default: true, null: false - t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true - end - - create_table "order_articles", id: :integer, force: :cascade do |t| - t.integer "order_id", default: 0, null: false - t.integer "article_id", default: 0, null: false - t.integer "quantity", default: 0, null: false - t.integer "tolerance", default: 0, null: false - t.integer "units_to_order", default: 0, null: false - t.integer "lock_version", default: 0, null: false - t.integer "article_price_id" - t.decimal "units_billed", precision: 8, scale: 3 - t.decimal "units_received", precision: 8, scale: 3 - t.index ["order_id", "article_id"], name: "index_order_articles_on_order_id_and_article_id", unique: true - t.index ["order_id"], name: "index_order_articles_on_order_id" - end - - create_table "order_comments", id: :integer, force: :cascade do |t| - t.integer "order_id" - t.integer "user_id" - t.text "text" - t.datetime "created_at" - t.index ["order_id"], name: "index_order_comments_on_order_id" - end - - create_table "orders", id: :integer, force: :cascade do |t| - t.integer "supplier_id" - t.text "note" - t.datetime "starts" - t.datetime "ends" - t.string "state", default: "open" - t.integer "lock_version", default: 0, null: false - t.integer "updated_by_user_id" - t.decimal "foodcoop_result", precision: 8, scale: 2 - t.integer "created_by_user_id" - t.datetime "boxfill" - t.integer "invoice_id" - t.date "pickup" - t.datetime "last_sent_mail" - t.integer "end_action", default: 0, null: false - t.decimal "transport", precision: 8, scale: 2 - t.index ["state"], name: "index_orders_on_state" - end - - create_table "page_versions", id: :integer, force: :cascade do |t| - t.integer "page_id" - t.integer "lock_version" - t.text "body" - t.integer "updated_by" - t.integer "redirect" - t.integer "parent_id" - t.datetime "updated_at" - t.index ["page_id"], name: "index_page_versions_on_page_id" - end - - create_table "pages", id: :integer, force: :cascade do |t| - t.string "title" - t.text "body" - t.string "permalink" - t.integer "lock_version", default: 0 - t.integer "updated_by" - t.integer "redirect" - t.integer "parent_id" - t.datetime "created_at" - t.datetime "updated_at" - t.index ["permalink"], name: "index_pages_on_permalink" - t.index ["title"], name: "index_pages_on_title" - end - - create_table "periodic_task_groups", id: :integer, force: :cascade do |t| - t.date "next_task_date" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "poll_choices", id: :integer, force: :cascade do |t| - t.integer "poll_vote_id", null: false - t.integer "choice", null: false - t.integer "value", null: false - t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true - end - - create_table "poll_votes", id: :integer, force: :cascade do |t| - t.integer "poll_id", null: false - t.integer "user_id", null: false - t.integer "ordergroup_id" - t.text "note" - t.datetime "created_at" - t.datetime "updated_at" - t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true - end - - create_table "polls", id: :integer, force: :cascade do |t| - t.integer "created_by_user_id", null: false - t.string "name", null: false - t.text "description" - t.datetime "starts" - t.datetime "ends" - t.boolean "one_vote_per_ordergroup", default: false, null: false - t.text "required_ordergroup_custom_fields" - t.text "required_user_custom_fields" - t.integer "voting_method", null: false - t.text "choices", null: false - t.integer "final_choice" - t.integer "multi_select_count", default: 0, null: false - t.integer "min_points" - t.integer "max_points" - t.datetime "created_at" - t.datetime "updated_at" - t.index ["final_choice"], name: "index_polls_on_final_choice" - end - - create_table "printer_job_updates", id: :integer, force: :cascade do |t| - t.integer "printer_job_id", null: false - t.datetime "created_at", null: false - t.string "state", null: false - t.text "message" - t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at" - end - - create_table "printer_jobs", id: :integer, force: :cascade do |t| - t.integer "order_id" - t.string "document", null: false - t.integer "created_by_user_id", null: false - t.integer "finished_by_user_id" - t.datetime "finished_at" - t.index ["finished_at"], name: "index_printer_jobs_on_finished_at" - end - - create_table "settings", id: :integer, force: :cascade do |t| - t.string "var", null: false - t.text "value" - t.integer "thing_id" - t.string "thing_type", limit: 30 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true - end - - create_table "stock_changes", id: :integer, force: :cascade do |t| - t.integer "stock_event_id" - t.integer "order_id" - t.integer "stock_article_id" - t.integer "quantity", default: 0 - t.datetime "created_at" - t.index ["stock_article_id"], name: "index_stock_changes_on_stock_article_id" - t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id" - end - - create_table "stock_events", id: :integer, force: :cascade do |t| - t.integer "supplier_id" - t.date "date" - t.datetime "created_at" - t.text "note" - t.integer "invoice_id" - t.string "type", null: false - t.index ["supplier_id"], name: "index_stock_events_on_supplier_id" - end - - create_table "supplier_categories", id: :integer, force: :cascade do |t| - t.string "name", null: false - t.string "description" - t.integer "financial_transaction_class_id" - t.integer "bank_account_id" - end - - create_table "suppliers", id: :integer, force: :cascade do |t| - t.string "name", default: "", null: false - t.string "address", default: "", null: false - t.string "phone", default: "", null: false - t.string "phone2" - t.string "fax" - t.string "email" - t.string "url" - t.string "contact_person" - t.string "customer_number" - t.string "delivery_days" - t.string "order_howto" - t.string "note" - t.integer "shared_supplier_id" - t.string "min_order_quantity" - t.datetime "deleted_at" - t.string "shared_sync_method" - t.string "iban" - t.integer "supplier_category_id" - t.index ["name"], name: "index_suppliers_on_name", unique: true - end - - create_table "tasks", id: :integer, force: :cascade do |t| - t.string "name", default: "", null: false - t.text "description" - t.date "due_date" - t.boolean "done", default: false - t.integer "workgroup_id" - t.datetime "created_on", null: false - t.datetime "updated_on", null: false - t.integer "required_users", default: 1 - t.integer "duration", default: 1 - t.integer "periodic_task_group_id" - t.integer "created_by_user_id" - t.index ["due_date"], name: "index_tasks_on_due_date" - t.index ["name"], name: "index_tasks_on_name" - t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id" - end - - create_table "users", id: :integer, force: :cascade do |t| - t.string "nick" - t.string "password_hash", default: "", null: false - t.string "password_salt", default: "", null: false - t.string "first_name", default: "", null: false - t.string "last_name", default: "", null: false - t.string "email", default: "", null: false - t.string "phone" - t.datetime "created_on", null: false - t.string "reset_password_token" - t.datetime "reset_password_expires" - t.datetime "last_login" - t.datetime "last_activity" - t.datetime "deleted_at" - t.string "iban" - t.index ["email"], name: "index_users_on_email", unique: true - t.index ["nick"], name: "index_users_on_nick", unique: true - end - +ActiveRecord::Schema.define(version: 20_231_210_181_807) do + create_table 'active_storage_attachments', options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.string 'record_type', null: false + t.bigint 'record_id', null: false + t.bigint 'blob_id', null: false + t.datetime 'created_at', null: false + t.index ['blob_id'], name: 'index_active_storage_attachments_on_blob_id' + t.index %w[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', unique: true + end + + create_table 'active_storage_blobs', options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'key', null: false + t.string 'filename', null: false + t.string 'content_type' + t.text 'metadata' + t.bigint 'byte_size', null: false + t.string 'checksum', null: false + t.datetime 'created_at', null: false + t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true + end + + create_table 'article_categories', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', default: '', null: false + t.string 'description' + t.index ['name'], name: 'index_article_categories_on_name', unique: true + end + + create_table 'article_unit_ratios', options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.bigint 'article_version_id', null: false + t.integer 'sort', null: false + t.decimal 'quantity', precision: 8, scale: 3, null: false + t.string 'unit' + t.index ['article_version_id'], name: 'index_article_unit_ratios_on_article_version_id' + t.index ['sort'], name: 'index_article_unit_ratios_on_sort' + end + + create_table 'article_units', id: false, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'unit', null: false + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['unit'], name: 'index_article_units_on_unit', unique: true + end + + create_table 'article_versions', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'article_id', null: false + t.decimal 'price', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'tax', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'deposit', precision: 8, scale: 2, default: '0.0', null: false + t.datetime 'created_at' + t.string 'name', default: '', null: false + t.integer 'article_category_id', default: 0, null: false + t.string 'unit' + t.string 'note' + t.boolean 'availability', default: true, null: false + t.string 'manufacturer' + t.string 'origin' + t.string 'order_number' + t.datetime 'updated_at' + t.string 'supplier_order_unit' + t.string 'price_unit' + t.string 'billing_unit' + t.string 'group_order_unit' + t.decimal 'group_order_granularity', precision: 8, scale: 3, default: '1.0', null: false + t.float 'minimum_order_quantity' + t.index ['article_category_id'], name: 'index_article_versions_on_article_category_id' + t.index %w[article_id created_at], name: 'index_article_versions_on_article_id_and_created_at', unique: true + end + + create_table 'articles', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'supplier_id', default: 0, null: false + t.datetime 'shared_updated_on' + t.datetime 'created_at' + t.datetime 'deleted_at' + t.string 'type' + t.integer 'quantity', default: 0 + t.index ['supplier_id'], name: 'index_articles_on_supplier_id' + t.index ['type'], name: 'index_articles_on_type' + end + + create_table 'assignments', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'user_id', default: 0, null: false + t.integer 'task_id', default: 0, null: false + t.boolean 'accepted', default: false + t.index %w[user_id task_id], name: 'index_assignments_on_user_id_and_task_id', unique: true + end + + create_table 'bank_accounts', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.string 'iban' + t.string 'description' + t.decimal 'balance', precision: 12, scale: 2, default: '0.0', null: false + t.datetime 'last_import' + t.string 'import_continuation_point' + t.integer 'bank_gateway_id' + end + + create_table 'bank_gateways', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.string 'url', null: false + t.string 'authorization' + t.integer 'unattended_user_id' + end + + create_table 'bank_transactions', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'bank_account_id', null: false + t.string 'external_id' + t.date 'date' + t.decimal 'amount', precision: 8, scale: 2, null: false + t.string 'iban' + t.string 'reference' + t.text 'text' + t.text 'receipt' + t.integer 'financial_link_id' + t.index ['financial_link_id'], name: 'index_bank_transactions_on_financial_link_id' + end + + create_table 'documents', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name' + t.string 'mime' + t.binary 'data', limit: 16_777_215 + t.integer 'created_by_user_id' + t.datetime 'created_at' + t.datetime 'updated_at' + t.integer 'parent_id' + t.index ['parent_id'], name: 'index_documents_on_parent_id' + end + + create_table 'financial_links', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=COMPACT', force: :cascade do |t| + t.text 'note' + end + + create_table 'financial_transaction_classes', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.boolean 'ignore_for_account_balance', default: false, null: false + end + + create_table 'financial_transaction_types', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.integer 'financial_transaction_class_id', null: false + t.string 'name_short' + t.integer 'bank_account_id' + t.index ['name_short'], name: 'index_financial_transaction_types_on_name_short' + end + + create_table 'financial_transactions', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'ordergroup_id' + t.decimal 'amount', precision: 8, scale: 2, default: '0.0', null: false + t.text 'note', null: false + t.integer 'user_id', default: 0, null: false + t.datetime 'created_on', null: false + t.integer 'financial_link_id' + t.integer 'financial_transaction_type_id', null: false + t.integer 'reverts_id' + t.integer 'group_order_id' + t.index ['ordergroup_id'], name: 'index_financial_transactions_on_ordergroup_id' + t.index ['reverts_id'], name: 'index_financial_transactions_on_reverts_id', unique: true + end + + create_table 'group_order_article_quantities', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'group_order_article_id', default: 0, null: false + t.decimal 'quantity', precision: 8, scale: 3, default: '0.0', null: false + t.decimal 'tolerance', precision: 8, scale: 3, default: '0.0', null: false + t.datetime 'created_on', null: false + t.index ['group_order_article_id'], name: 'index_group_order_article_quantities_on_group_order_article_id' + end + + create_table 'group_order_articles', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'group_order_id', default: 0, null: false + t.integer 'order_article_id', default: 0, null: false + t.decimal 'quantity', precision: 8, scale: 3, default: '0.0', null: false + t.decimal 'tolerance', precision: 8, scale: 3, default: '0.0', null: false + t.datetime 'updated_on', null: false + t.decimal 'result', precision: 8, scale: 3 + t.decimal 'result_computed', precision: 8, scale: 3 + t.index %w[group_order_id order_article_id], name: 'goa_index', unique: true + t.index ['group_order_id'], name: 'index_group_order_articles_on_group_order_id' + t.index ['order_article_id'], name: 'index_group_order_articles_on_order_article_id' + end + + create_table 'group_orders', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'ordergroup_id' + t.integer 'order_id', default: 0, null: false + t.decimal 'price', precision: 8, scale: 2, default: '0.0', null: false + t.integer 'lock_version', default: 0, null: false + t.datetime 'updated_on', null: false + t.integer 'updated_by_user_id' + t.decimal 'transport', precision: 8, scale: 2 + t.index ['order_id'], name: 'index_group_orders_on_order_id' + t.index %w[ordergroup_id order_id], name: 'index_group_orders_on_ordergroup_id_and_order_id', unique: true + t.index ['ordergroup_id'], name: 'index_group_orders_on_ordergroup_id' + end + + create_table 'groups', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'type', default: '', null: false + t.string 'name', default: '', null: false + t.string 'description' + t.decimal 'account_balance', precision: 12, scale: 2, default: '0.0', null: false + t.datetime 'created_on', null: false + t.boolean 'role_admin', default: false, null: false + t.boolean 'role_suppliers', default: false, null: false + t.boolean 'role_article_meta', default: false, null: false + t.boolean 'role_finance', default: false, null: false + t.boolean 'role_orders', default: false, null: false + t.datetime 'deleted_at' + t.string 'contact_person' + t.string 'contact_phone' + t.string 'contact_address' + t.text 'stats' + t.integer 'next_weekly_tasks_number', default: 8 + t.boolean 'ignore_apple_restriction', default: false + t.boolean 'role_invoices', default: false, null: false + t.date 'break_start' + t.date 'break_end' + t.boolean 'role_pickups', default: false, null: false + t.index ['name'], name: 'index_groups_on_name', unique: true + end + + create_table 'invites', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'token', default: '', null: false + t.datetime 'expires_at', null: false + t.integer 'group_id', default: 0, null: false + t.integer 'user_id', default: 0, null: false + t.string 'email', default: '', null: false + t.index ['token'], name: 'index_invites_on_token' + end + + create_table 'invoices', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'supplier_id' + t.string 'number' + t.date 'date' + t.date 'paid_on' + t.text 'note' + t.decimal 'amount', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'deposit', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'deposit_credit', precision: 8, scale: 2, default: '0.0', null: false + t.datetime 'created_at' + t.datetime 'updated_at' + t.integer 'created_by_user_id' + t.string 'attachment_mime' + t.binary 'attachment_data', limit: 16_777_215 + t.integer 'financial_link_id' + t.index ['supplier_id'], name: 'index_invoices_on_supplier_id' + end + + create_table 'links', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.string 'url', null: false + t.integer 'workgroup_id' + t.boolean 'indirect', default: false, null: false + t.string 'authorization' + end + + create_table 'mail_delivery_status', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.datetime 'created_at' + t.string 'email', null: false + t.string 'message', null: false + t.string 'attachment_mime' + t.binary 'attachment_data', limit: 16_777_215 + t.index ['email'], name: 'index_mail_delivery_status_on_email' + end + + create_table 'memberships', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'group_id', default: 0, null: false + t.integer 'user_id', default: 0, null: false + t.index %w[user_id group_id], name: 'index_memberships_on_user_id_and_group_id', unique: true + end + + create_table 'message_recipients', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'message_id', null: false + t.integer 'user_id', null: false + t.integer 'email_state', default: 0, null: false + t.datetime 'read_at' + t.index ['message_id'], name: 'index_message_recipients_on_message_id' + t.index %w[user_id read_at], name: 'index_message_recipients_on_user_id_and_read_at' + end + + create_table 'messages', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'sender_id' + t.string 'subject', null: false + t.text 'body' + t.boolean 'private', default: false + t.datetime 'created_at' + t.integer 'reply_to' + t.integer 'group_id' + t.string 'salt' + t.binary 'received_email', limit: 16_777_215 + end + + create_table 'oauth_access_grants', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'resource_owner_id', null: false + t.integer 'application_id', null: false + t.string 'token', null: false + t.integer 'expires_in', null: false + t.text 'redirect_uri', null: false + t.datetime 'created_at', null: false + t.datetime 'revoked_at' + t.string 'scopes' + t.index ['token'], name: 'index_oauth_access_grants_on_token', unique: true + end + + create_table 'oauth_access_tokens', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'resource_owner_id' + t.integer 'application_id' + t.string 'token', null: false + t.string 'refresh_token' + t.integer 'expires_in' + t.datetime 'revoked_at' + t.datetime 'created_at', null: false + t.string 'scopes' + t.index ['refresh_token'], name: 'index_oauth_access_tokens_on_refresh_token', unique: true + t.index ['resource_owner_id'], name: 'index_oauth_access_tokens_on_resource_owner_id' + t.index ['token'], name: 'index_oauth_access_tokens_on_token', unique: true + end + + create_table 'oauth_applications', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.string 'uid', null: false + t.string 'secret', null: false + t.text 'redirect_uri', null: false + t.string 'scopes', default: '', null: false + t.datetime 'created_at' + t.datetime 'updated_at' + t.boolean 'confidential', default: true, null: false + t.index ['uid'], name: 'index_oauth_applications_on_uid', unique: true + end + + create_table 'order_articles', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'order_id', default: 0, null: false + t.decimal 'quantity', precision: 8, scale: 3, default: '0.0', null: false + t.decimal 'tolerance', precision: 8, scale: 3, default: '0.0', null: false + t.decimal 'units_to_order', precision: 8, scale: 3, default: '0.0', null: false + t.integer 'lock_version', default: 0, null: false + t.integer 'article_version_id', null: false + t.decimal 'units_billed', precision: 8, scale: 3 + t.decimal 'units_received', precision: 8, scale: 3 + t.index ['article_version_id'], name: 'index_order_articles_on_article_version_id' + t.index %w[order_id article_version_id], name: 'index_order_articles_on_order_id_and_article_version_id', unique: true + t.index ['order_id'], name: 'index_order_articles_on_order_id' + end + + create_table 'order_comments', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'order_id' + t.integer 'user_id' + t.text 'text' + t.datetime 'created_at' + t.index ['order_id'], name: 'index_order_comments_on_order_id' + end + + create_table 'orders', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'supplier_id' + t.text 'note' + t.datetime 'starts' + t.datetime 'ends' + t.string 'state', default: 'open' + t.integer 'lock_version', default: 0, null: false + t.integer 'updated_by_user_id' + t.decimal 'foodcoop_result', precision: 8, scale: 2 + t.integer 'created_by_user_id' + t.datetime 'boxfill' + t.date 'pickup' + t.integer 'invoice_id' + t.datetime 'last_sent_mail' + t.integer 'end_action', default: 0, null: false + t.decimal 'transport', precision: 8, scale: 2 + t.index ['state'], name: 'index_orders_on_state' + end + + create_table 'page_versions', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'page_id' + t.integer 'lock_version' + t.text 'body' + t.integer 'updated_by' + t.integer 'redirect' + t.integer 'parent_id' + t.datetime 'updated_at' + t.index ['page_id'], name: 'index_page_versions_on_page_id' + end + + create_table 'pages', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'title' + t.text 'body' + t.string 'permalink' + t.integer 'lock_version', default: 0 + t.integer 'updated_by' + t.integer 'redirect' + t.integer 'parent_id' + t.datetime 'created_at' + t.datetime 'updated_at' + t.index ['permalink'], name: 'index_pages_on_permalink' + t.index ['title'], name: 'index_pages_on_title' + end + + create_table 'periodic_task_groups', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.date 'next_task_date' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'poll_choices', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'poll_vote_id', null: false + t.integer 'choice', null: false + t.integer 'value', null: false + t.index %w[poll_vote_id choice], name: 'index_poll_choices_on_poll_vote_id_and_choice', unique: true + end + + create_table 'poll_votes', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'poll_id', null: false + t.integer 'user_id', null: false + t.integer 'ordergroup_id' + t.text 'note' + t.datetime 'created_at' + t.datetime 'updated_at' + t.index %w[poll_id user_id ordergroup_id], name: 'index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id', unique: true + end + + create_table 'polls', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'created_by_user_id', null: false + t.string 'name', null: false + t.text 'description' + t.datetime 'starts' + t.datetime 'ends' + t.boolean 'one_vote_per_ordergroup', default: false, null: false + t.text 'required_ordergroup_custom_fields' + t.text 'required_user_custom_fields' + t.integer 'voting_method', null: false + t.text 'choices', null: false + t.integer 'final_choice' + t.integer 'multi_select_count', default: 0, null: false + t.integer 'min_points' + t.integer 'max_points' + t.datetime 'created_at' + t.datetime 'updated_at' + t.index ['final_choice'], name: 'index_polls_on_final_choice' + end + + create_table 'printer_job_updates', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'printer_job_id', null: false + t.datetime 'created_at', null: false + t.string 'state', null: false + t.text 'message' + t.index %w[printer_job_id created_at], name: 'index_printer_job_updates_on_printer_job_id_and_created_at' + end + + create_table 'printer_jobs', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'order_id' + t.string 'document', null: false + t.integer 'created_by_user_id', null: false + t.integer 'finished_by_user_id' + t.datetime 'finished_at' + t.index ['finished_at'], name: 'index_printer_jobs_on_finished_at' + end + + create_table 'settings', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'var', null: false + t.text 'value' + t.integer 'thing_id' + t.string 'thing_type', limit: 30 + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index %w[thing_type thing_id var], name: 'index_settings_on_thing_type_and_thing_id_and_var', unique: true + end + + create_table 'stock_changes', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'stock_event_id' + t.integer 'order_id' + t.integer 'stock_article_id' + t.integer 'quantity', default: 0 + t.datetime 'created_at' + t.index ['stock_article_id'], name: 'index_stock_changes_on_stock_article_id' + t.index ['stock_event_id'], name: 'index_stock_changes_on_stock_event_id' + end + + create_table 'stock_events', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.integer 'supplier_id' + t.date 'date' + t.datetime 'created_at' + t.text 'note' + t.integer 'invoice_id' + t.string 'type', null: false + t.index ['supplier_id'], name: 'index_stock_events_on_supplier_id' + end + + create_table 'supplier_categories', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', null: false + t.string 'description' + t.integer 'financial_transaction_class_id', null: false + t.integer 'bank_account_id' + end + + create_table 'suppliers', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', default: '', null: false + t.string 'address', default: '', null: false + t.string 'phone', default: '', null: false + t.string 'phone2' + t.string 'fax' + t.string 'email' + t.string 'url' + t.string 'contact_person' + t.string 'customer_number' + t.string 'delivery_days' + t.string 'order_howto' + t.string 'note' + t.string 'min_order_quantity' + t.datetime 'deleted_at' + t.string 'shared_sync_method' + t.string 'iban' + t.integer 'supplier_category_id', null: false + t.string 'supplier_remote_source' + t.string 'external_uuid' + t.datetime 'unit_migration_completed' + t.index ['external_uuid'], name: 'index_suppliers_on_external_uuid', unique: true + t.index ['name'], name: 'index_suppliers_on_name', unique: true + end + + create_table 'tasks', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'name', default: '', null: false + t.text 'description' + t.date 'due_date' + t.boolean 'done', default: false + t.integer 'workgroup_id' + t.datetime 'created_on', null: false + t.datetime 'updated_on', null: false + t.integer 'required_users', default: 1 + t.integer 'duration', default: 1 + t.integer 'periodic_task_group_id' + t.integer 'created_by_user_id' + t.index ['due_date'], name: 'index_tasks_on_due_date' + t.index ['name'], name: 'index_tasks_on_name' + t.index ['workgroup_id'], name: 'index_tasks_on_workgroup_id' + end + + create_table 'users', id: :integer, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', force: :cascade do |t| + t.string 'nick' + t.string 'password_hash', default: '', null: false + t.string 'password_salt', default: '', null: false + t.string 'first_name', default: '', null: false + t.string 'last_name', default: '', null: false + t.string 'email', default: '', null: false + t.string 'phone' + t.datetime 'created_on', null: false + t.string 'reset_password_token' + t.datetime 'reset_password_expires' + t.datetime 'last_login' + t.datetime 'last_activity' + t.datetime 'deleted_at' + t.string 'iban' + t.string 'attachment_mime' + t.binary 'attachment_data', limit: 16_777_215 + t.index ['email'], name: 'index_users_on_email', unique: true + t.index ['nick'], name: 'index_users_on_nick', unique: true + end + + add_foreign_key 'active_storage_attachments', 'active_storage_blobs', column: 'blob_id' end diff --git a/db/seeds/hackathon.seeds.rb b/db/seeds/hackathon.seeds.rb new file mode 100644 index 00000000..81d88fd3 --- /dev/null +++ b/db/seeds/hackathon.seeds.rb @@ -0,0 +1,158 @@ +require_relative 'seed_helper' + +## Financial transaction classes + +FinancialTransactionClass.create!(id: 1, name: 'Standard') +FinancialTransactionClass.create!(id: 2, name: 'Foodsoft') + +## Article units + +# TODO: - make this depend on locale - see https://github.com/foodcoopsat/foodsoft_hackathon/issues/35 +unit_codes = %w[GRM HGM KGM LTR MLT PTN STC XPP XCR XBO XBH XGR XPK XSA XPU XPT] +unit_codes.each { |unit_code| ArticleUnit.create!(unit: unit_code) } + +## Suppliers & articles + +SupplierCategory.create!(id: 1, name: 'Other', financial_transaction_class_id: 1) + +Supplier.create!([ + { id: 1, name: 'Hackathon', supplier_category_id: 1, address: 'Smallstreet 1, Cookilage', + phone: '0123456789', email: 'info@bbakery.test', min_order_quantity: '100', unit_migration_completed: Time.now } + ]) + +ArticleCategory.create!(id: 1, name: 'Other', description: 'other, misc, unknown') +ArticleCategory.create!(id: 2, name: 'Fruit') +ArticleCategory.create!(id: 3, name: 'Vegetables') +ArticleCategory.create!(id: 4, name: 'Potatoes & onions') +ArticleCategory.create!(id: 5, name: 'Bread & Bakery') +ArticleCategory.create!(id: 6, name: 'Drinks', description: 'juice, fruit juice, vegetable juice, soda') +ArticleCategory.create!(id: 7, name: 'Herbs & Spices') +ArticleCategory.create!(id: 8, name: 'Milk & products', + description: 'milk, butter, cream, yoghurt, cheese, eggs, milk substitutes') +ArticleCategory.create!(id: 9, name: 'Fish & Sea') +ArticleCategory.create!(id: 10, name: 'Meat') +ArticleCategory.create!(id: 11, name: 'Oils & Fats') +ArticleCategory.create!(id: 12, name: 'Grains & Legumes') +ArticleCategory.create!(id: 13, name: 'Nuts & Seeds') +ArticleCategory.create!(id: 14, name: 'Sugar & Sweets') + +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Carrots', article_category_id: 3, unit: nil, price: 3, tax: 7.0, + deposit: '0.0', supplier_order_unit: 'KGM', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.001 }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Pumpkin', article_category_id: 3, unit: nil, price: 1.5, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'XPP', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1.3, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Bread', article_category_id: 5, unit: nil, price: 2.1, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'XPP', group_order_granularity: 0.5, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 700, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Bread rolls', article_category_id: 5, unit: nil, price: 1, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'XPP', group_order_unit: 'XPP', minimum_order_quantity: 5, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 350, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Muesli', article_category_id: 13, unit: nil, price: 2.5, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'XPP', billing_unit: 'XPP', group_order_unit: 'XPP', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 500, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Smoked tofu', article_category_id: 8, unit: nil, price: 2.4, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'HGM', billing_unit: 'GRM', group_order_unit: 'XPP', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 160, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Beer', article_category_id: 6, unit: nil, price: 52, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XCR', price_unit: 'XBO', billing_unit: 'XBO', group_order_unit: 'XBO', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 20, unit: 'XBO' }), ArticleUnitRatio.new({ sort: 2, quantity: 10, unit: 'LTR' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Detergent', article_category_id: 1, unit: nil, price: 20, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'LTR', billing_unit: 'LTR', group_order_unit: 'LTR', group_order_granularity: 0.001, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 2, quantity: 20, unit: 'LTR' }), ArticleUnitRatio.new({ sort: 2, quantity: 25, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Rice', article_category_id: 12, unit: nil, price: 6.75, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Potatoes', article_category_id: 3, unit: nil, price: 1.5, tax: 7.0, + deposit: '0.0', supplier_order_unit: 'KGM', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'GRM' }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Wheat', article_category_id: 12, unit: nil, price: 25, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Oranges', article_category_id: 2, unit: nil, price: 36, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 12, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Lentils', article_category_id: 12, unit: nil, price: 2.7, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 500, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Oyster mushrooms', article_category_id: 3, unit: nil, + price: 3, tax: 7.0, deposit: '0.0', supplier_order_unit: 'KGM', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.001, minimum_order_quantity: 1.2 }) + +Supplier.create!([ + { id: 2, name: 'Unit migration test', supplier_category_id: 1, address: 'Smallstreet 2, Cookilage', + phone: '0123456789', email: 'info@bbakery.test', min_order_quantity: '100', unit_migration_completed: nil } + ]) + +Article.create!({ supplier_id: 2, + quantity: 0 }).article_versions.create({ name: 'Goat cheese', article_category_id: 8, unit: '250g', price: 3, tax: 7.0, + deposit: '0.0', supplier_order_unit: nil, price_unit: nil, billing_unit: nil, group_order_unit: nil, group_order_granularity: 1 }) +Article.create!({ supplier_id: 2, + quantity: 0 }).article_versions.create({ name: 'Butter', article_category_id: 8, unit: '4x250g', price: 3, tax: 7.0, deposit: '0.0', supplier_order_unit: nil, price_unit: 'XPP', billing_unit: 'XPP', group_order_unit: 'XPP', group_order_granularity: 1, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 4, unit: 'XPP' })] }) +Article.create!({ supplier_id: 2, + quantity: 0 }).article_versions.create({ name: 'Bread', article_category_id: 5, unit: 'pc', price: 3, tax: 7.0, + deposit: '0.0', supplier_order_unit: nil, price_unit: nil, billing_unit: nil, group_order_unit: nil, group_order_granularity: 1 }) + +## Members & groups + +User.create!(id: 1, nick: 'admin', password: 'secret', first_name: 'Anton', last_name: 'Administrator', + email: 'admin@foo.test', created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00') +User.create!(id: 2, nick: 'john', password: 'secret', first_name: 'John', last_name: 'Doe', email: 'john@doe.test', + created_on: 'Sun, 19 Jan 2014 17:38:22 UTC +00:00') +User.create!(id: 3, nick: 'peter', password: 'secret', first_name: 'Peter', last_name: 'Peters', + email: 'peter@peters.test', created_on: 'Sat, 25 Jan 2014 20:20:36 UTC +00:00') +User.create!(id: 4, nick: 'jan', password: 'secret', first_name: 'Jan', last_name: 'Lou', email: 'jan@lou.test', + created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00') +User.create!(id: 5, nick: 'mary', password: 'secret', first_name: 'Mary', last_name: 'Lou', email: 'marie@lou.test', + created_on: 'Mon, 03 Feb 2014 11:47:17 UTC +00:00') + +Workgroup.create!(id: 1, name: 'Administrators', description: 'System administrators.', account_balance: 0.0, created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', role_admin: true, role_suppliers: true, role_article_meta: true, role_finance: true, role_orders: true, + next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 2, name: 'Finances', account_balance: 0.0, created_on: 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: true, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 3, name: 'Ordering', account_balance: 0.0, created_on: 'Thu, 20 Feb 2014 14:44:47 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: true, role_finance: false, role_orders: true, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 4, name: 'Assortment', account_balance: 0.0, created_on: 'Wed, 09 Apr 2014 12:24:55 UTC +00:00', + role_admin: false, role_suppliers: true, role_article_meta: true, role_finance: false, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 5, name: 'Admin Administrator', account_balance: 0.0, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, stats: { jobs_size: 0, orders_sum: 1021.74 }, + next_weekly_tasks_number: 8, ignore_apple_restriction: true) +Ordergroup.create!(id: 6, name: "Pete's house", account_balance: -0.35E2, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Piet Pieterssen', + stats: { jobs_size: 0, orders_sum: 60.96 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 7, name: 'Jan Klaassen', account_balance: -0.35E2, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Jan Klaassen', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 8, name: 'John Doe', account_balance: 0.90E2, created_on: 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'John Doe', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) + +Membership.create!(group_id: 1, user_id: 1) +Membership.create!(group_id: 5, user_id: 1) +Membership.create!(group_id: 2, user_id: 2) +Membership.create!(group_id: 8, user_id: 2) +Membership.create!(group_id: 6, user_id: 3) +Membership.create!(group_id: 7, user_id: 4) +Membership.create!(group_id: 8, user_id: 4) +Membership.create!(group_id: 3, user_id: 4) +Membership.create!(group_id: 7, user_id: 5) +Membership.create!(group_id: 3, user_id: 5) +Membership.create!(group_id: 4, user_id: 5) + +## Orders & OrderArticles + +seed_order(supplier_id: 1, starts: 2.days.ago, ends: 5.days.from_now) + +## Finances + +FinancialTransactionType.create!(id: 1, name: 'Foodcoop', financial_transaction_class_id: 1) + +FinancialTransaction.create!(id: 1, ordergroup_id: 5, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 3, ordergroup_id: 6, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 4, ordergroup_id: 7, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 5, ordergroup_id: 5, amount: 0.35E2, note: 'payment', user_id: 2, + created_on: 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 6, ordergroup_id: 8, amount: 0.90E2, note: 'Bank transfer', user_id: 2, + created_on: 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', financial_transaction_type_id: 1) + +FoodsoftConfig[:minimum_balance] = '-10000' diff --git a/db/seeds/hackathon_de.seeds.rb b/db/seeds/hackathon_de.seeds.rb new file mode 100644 index 00000000..3e645e47 --- /dev/null +++ b/db/seeds/hackathon_de.seeds.rb @@ -0,0 +1,158 @@ +require_relative 'seed_helper' + +## Financial transaction classes + +FinancialTransactionClass.create!(id: 1, name: 'Standard') +FinancialTransactionClass.create!(id: 2, name: 'Foodsoft') + +## Article units + +# TODO: - make this depend on locale - see https://github.com/foodcoopsat/foodsoft_hackathon/issues/35 +unit_codes = %w[GRM HGM KGM LTR MLT PTN STC XPP XCR XBO XBH XGR XPK XSA XPU XPT] +unit_codes.each { |unit_code| ArticleUnit.create!(unit: unit_code) } + +## Suppliers & articles + +SupplierCategory.create!(id: 1, name: 'Other', financial_transaction_class_id: 1) + +Supplier.create!([ + { id: 1, name: 'Hackathon', supplier_category_id: 1, address: 'Smallstreet 1, Cookilage', + phone: '0123456789', email: 'info@bbakery.test', min_order_quantity: '100', unit_migration_completed: Time.now } + ]) + +ArticleCategory.create!(id: 1, name: 'Sonstiges') +ArticleCategory.create!(id: 2, name: 'Obst') +ArticleCategory.create!(id: 3, name: 'Gemüse') +ArticleCategory.create!(id: 4, name: 'Kartoffel & Zwiebel') +ArticleCategory.create!(id: 5, name: 'Backwaren') +ArticleCategory.create!(id: 6, name: 'Getränke') +ArticleCategory.create!(id: 7, name: 'Kräuter & Gewürze') +ArticleCategory.create!(id: 8, name: 'Mich & Milchprodukte') +ArticleCategory.create!(id: 9, name: 'Fisch & Meeresfrüchte') +ArticleCategory.create!(id: 10, name: 'Fleisch') +ArticleCategory.create!(id: 11, name: 'Öle & Fett') +ArticleCategory.create!(id: 12, name: 'Getreide & Bohnen') +ArticleCategory.create!(id: 13, name: 'Nüsse & Samen') +ArticleCategory.create!(id: 14, name: 'Zucker & Süßigkeiten') + +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Karotten', article_category_id: 3, unit: nil, price: 3, tax: 7.0, + deposit: '0.0', supplier_order_unit: 'KGM', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.001 }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Kürbis', article_category_id: 3, unit: nil, price: 1.5, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'XPP', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1.3, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Brot', article_category_id: 5, unit: nil, price: 2.1, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'XPP', group_order_granularity: 0.5, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 700, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Semmeln', article_category_id: 5, unit: nil, price: 1, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'XPP', group_order_unit: 'XPP', minimum_order_quantity: 5, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 350, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Müsli', article_category_id: 13, unit: nil, price: 2.5, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'XPP', billing_unit: 'XPP', group_order_unit: 'XPP', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 500, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Räuchertofu', article_category_id: 8, unit: nil, price: 2.4, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'HGM', billing_unit: 'GRM', group_order_unit: 'XPP', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 160, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Bier', article_category_id: 6, unit: nil, price: 52, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XCR', price_unit: 'XBO', billing_unit: 'XBO', group_order_unit: 'XBO', + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 20, unit: 'XBO' }), ArticleUnitRatio.new({ sort: 2, quantity: 10, unit: 'LTR' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Waschmittel', article_category_id: 1, unit: nil, price: 20, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'LTR', billing_unit: 'LTR', group_order_unit: 'LTR', group_order_granularity: 0.001, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 2, quantity: 20, unit: 'LTR' }), ArticleUnitRatio.new({ sort: 2, quantity: 25, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Reis', article_category_id: 12, unit: nil, price: 6.75, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Kartoffeln', article_category_id: 3, unit: nil, price: 1.5, tax: 7.0, + deposit: '0.0', supplier_order_unit: 'KGM', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'GRM' }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Weizen', article_category_id: 12, unit: nil, price: 25, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Orangen', article_category_id: 2, unit: nil, price: 36, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 12, unit: 'KGM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Linsen', article_category_id: 12, unit: nil, price: 2.7, tax: 7.0, deposit: '0.0', supplier_order_unit: 'XPP', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.05, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 500, unit: 'GRM' })] }) +Article.create!({ supplier_id: 1, + quantity: 0 }).article_versions.create({ name: 'Austernpilze', article_category_id: 3, unit: nil, + price: 3, tax: 7.0, deposit: '0.0', supplier_order_unit: 'KGM', price_unit: 'KGM', billing_unit: 'KGM', group_order_unit: 'KGM', group_order_granularity: 0.001, minimum_order_quantity: 1.2 }) + +Supplier.create!([ + { id: 2, name: 'Einheiten-Migrations-Test', supplier_category_id: 1, + address: 'Smallstreet 2, Cookilage', phone: '0123456789', email: 'info@bbakery.test', min_order_quantity: '100', unit_migration_completed: nil } + ]) + +Article.create!({ supplier_id: 2, + quantity: 0 }).article_versions.create({ name: 'Ziegenkäse', article_category_id: 8, unit: '250g', price: 3, tax: 7.0, + deposit: '0.0', supplier_order_unit: nil, price_unit: nil, billing_unit: nil, group_order_unit: nil, group_order_granularity: 1 }) +Article.create!({ supplier_id: 2, + quantity: 0 }).article_versions.create({ name: 'Butter', article_category_id: 8, unit: '4x250g', price: 3, tax: 7.0, deposit: '0.0', supplier_order_unit: nil, price_unit: 'XPP', billing_unit: 'XPP', group_order_unit: 'XPP', group_order_granularity: 1, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 4, unit: 'XPP' })] }) +Article.create!({ supplier_id: 2, + quantity: 0 }).article_versions.create({ name: 'Brot', article_category_id: 5, unit: 'Stk', price: 3, tax: 7.0, + deposit: '0.0', supplier_order_unit: nil, price_unit: nil, billing_unit: nil, group_order_unit: nil, group_order_granularity: 1 }) + +## Members & groups + +User.create!(id: 1, nick: 'admin', password: 'secret', first_name: 'Anton', last_name: 'Administrator', + email: 'admin@foo.test', created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00') +User.create!(id: 2, nick: 'john', password: 'secret', first_name: 'John', last_name: 'Doe', email: 'john@doe.test', + created_on: 'Sun, 19 Jan 2014 17:38:22 UTC +00:00') +User.create!(id: 3, nick: 'peter', password: 'secret', first_name: 'Peter', last_name: 'Peters', + email: 'peter@peters.test', created_on: 'Sat, 25 Jan 2014 20:20:36 UTC +00:00') +User.create!(id: 4, nick: 'jan', password: 'secret', first_name: 'Jan', last_name: 'Lou', email: 'jan@lou.test', + created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00') +User.create!(id: 5, nick: 'mary', password: 'secret', first_name: 'Mary', last_name: 'Lou', email: 'marie@lou.test', + created_on: 'Mon, 03 Feb 2014 11:47:17 UTC +00:00') +User.find(1).settings['profile'] = { language: :de } + +Workgroup.create!(id: 1, name: 'Administrators', description: 'System administrators.', account_balance: 0.0, created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', role_admin: true, role_suppliers: true, role_article_meta: true, role_finance: true, role_orders: true, + next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 2, name: 'Finances', account_balance: 0.0, created_on: 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: true, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 3, name: 'Ordering', account_balance: 0.0, created_on: 'Thu, 20 Feb 2014 14:44:47 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: true, role_finance: false, role_orders: true, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 4, name: 'Assortment', account_balance: 0.0, created_on: 'Wed, 09 Apr 2014 12:24:55 UTC +00:00', + role_admin: false, role_suppliers: true, role_article_meta: true, role_finance: false, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 5, name: 'Admin Administrator', account_balance: 0.0, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, stats: { jobs_size: 0, orders_sum: 1021.74 }, + next_weekly_tasks_number: 8, ignore_apple_restriction: true) +Ordergroup.create!(id: 6, name: "Pete's house", account_balance: -0.35E2, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Piet Pieterssen', + stats: { jobs_size: 0, orders_sum: 60.96 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 7, name: 'Jan Klaassen', account_balance: -0.35E2, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Jan Klaassen', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 8, name: 'John Doe', account_balance: 0.90E2, created_on: 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'John Doe', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) + +Membership.create!(group_id: 1, user_id: 1) +Membership.create!(group_id: 5, user_id: 1) +Membership.create!(group_id: 2, user_id: 2) +Membership.create!(group_id: 8, user_id: 2) +Membership.create!(group_id: 6, user_id: 3) +Membership.create!(group_id: 7, user_id: 4) +Membership.create!(group_id: 8, user_id: 4) +Membership.create!(group_id: 3, user_id: 4) +Membership.create!(group_id: 7, user_id: 5) +Membership.create!(group_id: 3, user_id: 5) +Membership.create!(group_id: 4, user_id: 5) + +## Orders & OrderArticles + +seed_order(supplier_id: 1, starts: 2.days.ago, ends: 5.days.from_now) + +## Finances + +FinancialTransactionType.create!(id: 1, name: 'Foodcoop', financial_transaction_class_id: 1) + +FinancialTransaction.create!(id: 1, ordergroup_id: 5, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 3, ordergroup_id: 6, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 4, ordergroup_id: 7, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 5, ordergroup_id: 5, amount: 0.35E2, note: 'payment', user_id: 2, + created_on: 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 6, ordergroup_id: 8, amount: 0.90E2, note: 'Bank transfer', user_id: 2, + created_on: 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', financial_transaction_type_id: 1) + +FoodsoftConfig[:minimum_balance] = '-10000' diff --git a/db/seeds/minimal.seeds.rb b/db/seeds/minimal.seeds.rb index d38ef10e..bbf97e10 100644 --- a/db/seeds/minimal.seeds.rb +++ b/db/seeds/minimal.seeds.rb @@ -2,30 +2,30 @@ # Create working group with full rights administrators = Workgroup.create!( - :name => "Administrators", - :description => "System administrators.", - :role_admin => true, - :role_finance => true, - :role_article_meta => true, - :role_pickups => true, - :role_suppliers => true, - :role_orders => true + name: 'Administrators', + description: 'System administrators.', + role_admin: true, + role_finance: true, + role_article_meta: true, + role_pickups: true, + role_suppliers: true, + role_orders: true ) # Create admin user User.create!( - :nick => "admin", - :first_name => "Anton", - :last_name => "Administrator", - :email => "admin@foo.test", - :password => "secret", - :groups => [administrators] + nick: 'admin', + first_name: 'Anton', + last_name: 'Administrator', + email: 'admin@foo.test', + password: 'secret', + groups: [administrators] ) # First entry for financial transaction types -financial_transaction_class = FinancialTransactionClass.create!(:name => "Other") -FinancialTransactionType.create!(:name => "Foodcoop", :financial_transaction_class_id => financial_transaction_class.id) +financial_transaction_class = FinancialTransactionClass.create!(name: 'Other') +FinancialTransactionType.create!(name: 'Foodcoop', financial_transaction_class_id: financial_transaction_class.id) # First entry for article categories -SupplierCategory.create!(:name => "Other", :financial_transaction_class_id => financial_transaction_class.id) -ArticleCategory.create!(:name => "Other", :description => "other, misc, unknown") +SupplierCategory.create!(name: 'Other', financial_transaction_class_id: financial_transaction_class.id) +ArticleCategory.create!(name: 'Other', description: 'other, misc, unknown') diff --git a/db/seeds/seed_helper.rb b/db/seeds/seed_helper.rb index 574be356..f46fc863 100644 --- a/db/seeds/seed_helper.rb +++ b/db/seeds/seed_helper.rb @@ -8,10 +8,10 @@ def seed_group_orders # order 3..12 times a random article go = og.group_orders.create!(order: order, updated_by_user_id: 1) - (3 + rand(10)).times do + rand(3..12).times do goa = go.group_order_articles.find_or_create_by!(order_article: order.order_articles.offset(rand(noas)).first) - unit_quantity = goa.order_article.price.unit_quantity - goa.update_quantities rand([4, 2 * unit_quantity + 2].max), rand(unit_quantity) + unit_quantity = goa.order_article.article_version.unit_quantity + goa.update_quantities rand([4, (2 * unit_quantity) + 2].max), rand(unit_quantity) end end # update totals diff --git a/db/seeds/small.en.seeds.rb b/db/seeds/small.en.seeds.rb index 40c338ac..71dd6c9b 100644 --- a/db/seeds/small.en.seeds.rb +++ b/db/seeds/small.en.seeds.rb @@ -1,173 +1,299 @@ -require_relative 'seed_helper.rb' +require_relative 'seed_helper' ## Financial transaction classes -FinancialTransactionClass.create!(:id => 1, :name => 'Standard') -FinancialTransactionClass.create!(:id => 2, :name => 'Foodsoft') +FinancialTransactionClass.create!(id: 1, name: 'Standard') +FinancialTransactionClass.create!(id: 2, name: 'Foodsoft') ## Suppliers & articles -SupplierCategory.create!(:id => 1, :name => "Other", :financial_transaction_class_id => 1) +SupplierCategory.create!(id: 1, name: 'Other', financial_transaction_class_id: 1) Supplier.create!([ - { :id => 1, :name => "Beautiful bakery", :supplier_category_id => 1, :address => "Smallstreet 1, Cookilage", :phone => "0123456789", :email => "info@bbakery.test", :min_order_quantity => "100" }, - { :id => 2, :name => "Chocolatiers", :supplier_category_id => 1, :address => "Multatuliroad 1, Amsterdam", :phone => "0123456789", :email => "info@chocolatiers.test", :url => "http://www.chocolatiers.test/", :contact_person => "Max Pure", :delivery_days => "Tue, Fr (Amsterdam)" }, - { :id => 3, :name => "Cheesemaker", :supplier_category_id => 1, :address => "Cheesestreet 5, London", :phone => "0123456789", :url => "http://www.cheesemaker.test/" }, - { :id => 4, :name => "The Nuthome", :supplier_category_id => 1, :address => "Alexanderplatz, Berlin", :phone => "0123456789", :email => "info@thenuthome.test", :url => "http://www.thenuthome.test/", :note => "delivery in Berlin; €9 delivery costs for orders under €123" } + { id: 1, name: 'Beautiful bakery', supplier_category_id: 1, address: 'Smallstreet 1, Cookilage', + phone: '0123456789', email: 'info@bbakery.test', min_order_quantity: '100' }, + { id: 2, name: 'Chocolatiers', supplier_category_id: 1, address: 'Multatuliroad 1, Amsterdam', + phone: '0123456789', email: 'info@chocolatiers.test', url: 'http://www.chocolatiers.test/', contact_person: 'Max Pure', delivery_days: 'Tue, Fr (Amsterdam)' }, + { id: 3, name: 'Cheesemaker', supplier_category_id: 1, address: 'Cheesestreet 5, London', + phone: '0123456789', url: 'http://www.cheesemaker.test/' }, + { id: 4, name: 'The Nuthome', supplier_category_id: 1, address: 'Alexanderplatz, Berlin', + phone: '0123456789', email: 'info@thenuthome.test', url: 'http://www.thenuthome.test/', note: 'delivery in Berlin; €9 delivery costs for orders under €123' } ]) -ArticleCategory.create!(:id => 1, :name => "Other", :description => "other, misc, unknown") -ArticleCategory.create!(:id => 2, :name => "Fruit") -ArticleCategory.create!(:id => 3, :name => "Vegetables") -ArticleCategory.create!(:id => 4, :name => "Potatoes & onions") -ArticleCategory.create!(:id => 5, :name => "Bread & Bakery") -ArticleCategory.create!(:id => 6, :name => "Drinks", :description => "juice, fruit juice, vegetable juice, soda") -ArticleCategory.create!(:id => 7, :name => "Herbs & Spices") -ArticleCategory.create!(:id => 8, :name => "Milk & products", :description => "milk, butter, cream, yoghurt, cheese, eggs, milk substitutes") -ArticleCategory.create!(:id => 9, :name => "Fish & Sea") -ArticleCategory.create!(:id => 10, :name => "Meat") -ArticleCategory.create!(:id => 11, :name => "Oils & Fats") -ArticleCategory.create!(:id => 12, :name => "Grains & Legumes") -ArticleCategory.create!(:id => 13, :name => "Nuts & Seeds") -ArticleCategory.create!(:id => 14, :name => "Sugar & Sweets") - -Article.create!(:name => "Brown whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brown half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brown sesame whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brown sesame half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Light wheat whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Light wheat half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bread with sunflower seeds whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bread with sunflower seeds half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bread with walnuts whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bread with walnuts half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Kennemerlandbread whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Kennemerlandbread half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Maize bread whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Maize bread half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 1200 gram whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 1200 gram half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 900 gram whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 900 gram half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Speltbread whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Speltbread half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Country bread 900gram whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Country bread 900gram half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "White whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "White half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "White with poppy seeds whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "White with poppy seeds half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Fig bread whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Fig bread half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Beer-based bread whole", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Beer-based bread half", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Raisin bun", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.99E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Muesli bun", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brioche", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.99E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brown croissant", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Croissants", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Cheese croissants", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocolatecroissants", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Soepstengels white", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Soepstengels volkoren", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.99E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Pumpkin-seed buns", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.88E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "White buns", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.66E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brown buns", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.66E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Tomato-feta bread", :supplier_id => 1, :article_category_id => 5, :unit => "pc", :note => "organic", :availability => true, :manufacturer => "The Baker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocolate Bar Milk (37%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "organic", :availability => true, :manufacturer => "Chocolatemakers", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocolate Bar Pure (68%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "organic", :availability => true, :manufacturer => "Chocolatemakers", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocolate Bar Milk (40%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "organic", :availability => true, :manufacturer => "Chocolatemakers", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocolate Bar Pure (75%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "organic", :availability => true, :manufacturer => "Chocolatemakers", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocolate Bar Swan Pure (75%)", :supplier_id => 2, :article_category_id => 14, :unit => "120gr", :note => "organic", :availability => true, :manufacturer => "Chocolatemakers", :origin => "NL", :price => 0.66E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Cacao nibs", :supplier_id => 2, :article_category_id => 14, :unit => "1 kg", :note => "organic", :availability => true, :manufacturer => "Chocolatemakers", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Cheese Cow-young", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.88E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese cow- young matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.99E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese cow- matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 12) -Article.create!(:name => "Cheese cow- extra matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.12E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "cheese Cow- old", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "cheese cow -very old", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.12E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese Cow-nettle young", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.99E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese cow- nettle young matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.1075E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese cow- nettle matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese Cow-cumin young", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.99E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese cow- cumin young matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.1075E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cheese cow- cumin matured", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "organic", :availability => true, :manufacturer => "Cheesefarm", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cashew nuts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.4444E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 22, :order_number => ":b936051") -Article.create!(:name => "Hazel white", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":9e3f85b") -Article.create!(:name => "Hazel brown", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":d278041") -Article.create!(:name => "Almond Brown Spanish", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.999E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":0b51a8d") -Article.create!(:name => "Brazil nuts (organic)", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.6666E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 20, :order_number => ":01e59e3") -Article.create!(:name => "Organic walnut light halves", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.333E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":7ff8587") -Article.create!(:name => "Pinenuts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 25, :order_number => ":aa88d9f") -Article.create!(:name => "Pumpkin", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 25, :order_number => ":e63069b") -Article.create!(:name => "Sunflower seeds (organic)", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.999E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 25, :order_number => ":0428388") -Article.create!(:name => "Amandel White Spaans", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "organic", :availability => true, :price => 0.66666E3, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":a8f0734") -Article.create!(:name => "Cashew", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.6666E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":1d26958") -Article.create!(:name => "Almonds blanched", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.333E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":31439e2") -Article.create!(:name => "Almonds natural", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":9c49374") -Article.create!(:name => "Walnut ELH halves", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.4444E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":92907d1") -Article.create!(:name => "Walnut ELP parts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.8888E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":395640e") -Article.create!(:name => "Brazil nuts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.8888E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":710acbb") -Article.create!(:name => "Macadamia type 0", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":bbaf40b") -Article.create!(:name => "Pecan", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55555E3, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":7958183") -Article.create!(:name => "Hazelnuts natural", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.6666E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":50392a8") -Article.create!(:name => "Hazelnuts blanched", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":4fe6525") -Article.create!(:name => "Mixed Nuts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.333E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":c051b22") -Article.create!(:name => "Peanuts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.777E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":f507577") -Article.create!(:name => "Small peanuts", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.8888E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":ce563bb") -Article.create!(:name => "Medjoul dates", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":8232061") -Article.create!(:name => "Turkish apricots natural", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":185084f") -Article.create!(:name => "Turkish apricots sulfurised", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":2b2fb20") -Article.create!(:name => "Spanish Figs", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.444E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":82590b1") -Article.create!(:name => "Turkish Figs", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.555E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":cabeeb6") -Article.create!(:name => "Sour Apricots South-Africa", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":2ac18b7") -Article.create!(:name => "Blue raisins Flames", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":16bfa75") -Article.create!(:name => "Yellow Raisins", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.2222E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":1c59324") -Article.create!(:name => "Red Raisins", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":c3fcd84") -Article.create!(:name => "Cranberries whole", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.222E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":921c168") -Article.create!(:name => "Dried apples", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.555E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":902c67b") -Article.create!(:name => "Dried plums without core", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.222E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":a847f91") -Article.create!(:name => "Pumpkin seeds", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.111E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":535645f") -Article.create!(:name => "Sunflower seeds", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.666E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":4ab9a83") -Article.create!(:name => "Linseed", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":04be223") -Article.create!(:name => "Poppy seeds", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.7777E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":ec5b2b9") -Article.create!(:name => "Pine nuts medium china", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.2222E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":0e5b0b8") -Article.create!(:name => "Goji berries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":d52ee00") -Article.create!(:name => "Mulberries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.5555E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":5f46bd5") -Article.create!(:name => "Peeled Hemp", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.5555E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":c39165b") -Article.create!(:name => "Incaberries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":8d44fe7") -Article.create!(:name => "Blueberries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.2222E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":9a95422") -Article.create!(:name => "Chia seeds", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55555E3, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":416d57b") -Article.create!(:name => "Coconut grated", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":b3f65e4") +ArticleCategory.create!(id: 1, name: 'Other', description: 'other, misc, unknown') +ArticleCategory.create!(id: 2, name: 'Fruit') +ArticleCategory.create!(id: 3, name: 'Vegetables') +ArticleCategory.create!(id: 4, name: 'Potatoes & onions') +ArticleCategory.create!(id: 5, name: 'Bread & Bakery') +ArticleCategory.create!(id: 6, name: 'Drinks', description: 'juice, fruit juice, vegetable juice, soda') +ArticleCategory.create!(id: 7, name: 'Herbs & Spices') +ArticleCategory.create!(id: 8, name: 'Milk & products', + description: 'milk, butter, cream, yoghurt, cheese, eggs, milk substitutes') +ArticleCategory.create!(id: 9, name: 'Fish & Sea') +ArticleCategory.create!(id: 10, name: 'Meat') +ArticleCategory.create!(id: 11, name: 'Oils & Fats') +ArticleCategory.create!(id: 12, name: 'Grains & Legumes') +ArticleCategory.create!(id: 13, name: 'Nuts & Seeds') +ArticleCategory.create!(id: 14, name: 'Sugar & Sweets') + +Article.create!(name: 'Brown whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brown half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brown sesame whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brown sesame half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Light wheat whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Light wheat half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bread with sunflower seeds whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bread with sunflower seeds half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bread with walnuts whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bread with walnuts half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Kennemerlandbread whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Kennemerlandbread half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Maize bread whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Maize bread half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 1200 gram whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 1200 gram half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 900 gram whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 900 gram half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Speltbread whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Speltbread half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Country bread 900gram whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Country bread 900gram half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'White whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'White half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'White with poppy seeds whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'White with poppy seeds half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Fig bread whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Fig bread half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Beer-based bread whole', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Beer-based bread half', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Raisin bun', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.99E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Muesli bun', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brioche', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.99E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brown croissant', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Croissants', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Cheese croissants', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocolatecroissants', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Soepstengels white', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Soepstengels volkoren', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.99E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Pumpkin-seed buns', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.88E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'White buns', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.66E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brown buns', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.66E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Tomato-feta bread', supplier_id: 1, article_category_id: 5, unit: 'pc', note: 'organic', availability: true, manufacturer: 'The Baker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocolate Bar Milk (37%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'organic', availability: true, manufacturer: 'Chocolatemakers', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocolate Bar Pure (68%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'organic', availability: true, manufacturer: 'Chocolatemakers', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocolate Bar Milk (40%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'organic', availability: true, manufacturer: 'Chocolatemakers', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocolate Bar Pure (75%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'organic', availability: true, manufacturer: 'Chocolatemakers', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocolate Bar Swan Pure (75%)', supplier_id: 2, article_category_id: 14, unit: '120gr', note: 'organic', availability: true, manufacturer: 'Chocolatemakers', origin: 'NL', price: 0.66E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Cacao nibs', supplier_id: 2, article_category_id: 14, unit: '1 kg', note: 'organic', availability: true, manufacturer: 'Chocolatemakers', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Cheese Cow-young', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.88E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- young matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.99E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 12, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- extra matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.12E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'cheese Cow- old', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'cheese cow -very old', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.12E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese Cow-nettle young', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.99E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- nettle young matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.1075E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- nettle matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese Cow-cumin young', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.99E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- cumin young matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.1075E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cheese cow- cumin matured', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'organic', availability: true, manufacturer: 'Cheesefarm', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cashew nuts', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', + availability: true, price: 0.4444E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 22, unit: 'XPP' })], order_number: ':b936051') +Article.create!(name: 'Hazel white', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', + availability: true, price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], order_number: ':9e3f85b') +Article.create!(name: 'Hazel brown', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], order_number: ':d278041') +Article.create!(name: 'Almond Brown Spanish', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', availability: true, price: 0.999E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], + order_number: ':0b51a8d') +Article.create!(name: 'Brazil nuts (organic)', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', availability: true, price: 0.6666E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 20, unit: 'XPP' })], + order_number: ':01e59e3') +Article.create!(name: 'Organic walnut light halves', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', availability: true, price: 0.333E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], + order_number: ':7ff8587') +Article.create!(name: 'Pinenuts', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', + availability: true, price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'XPP' })], order_number: ':aa88d9f') +Article.create!(name: 'Pumpkin', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'XPP' })], order_number: ':e63069b') +Article.create!(name: 'Sunflower seeds (organic)', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', availability: true, price: 0.999E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'XPP' })], + order_number: ':0428388') +Article.create!(name: 'Amandel White Spaans', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'organic', availability: true, price: 0.66666E3, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], + order_number: ':a8f0734') +Article.create!(name: 'Cashew', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.6666E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':1d26958') +Article.create!(name: 'Almonds blanched', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.333E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':31439e2') +Article.create!(name: 'Almonds natural', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':9c49374') +Article.create!(name: 'Walnut ELH halves', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.4444E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':92907d1') +Article.create!(name: 'Walnut ELP parts', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.8888E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':395640e') +Article.create!(name: 'Brazil nuts', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.8888E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':710acbb') +Article.create!(name: 'Macadamia type 0', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':bbaf40b') +Article.create!(name: 'Pecan', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55555E3, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':7958183') +Article.create!(name: 'Hazelnuts natural', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.6666E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':50392a8') +Article.create!(name: 'Hazelnuts blanched', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':4fe6525') +Article.create!(name: 'Mixed Nuts', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.333E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':c051b22') +Article.create!(name: 'Peanuts', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.777E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':f507577') +Article.create!(name: 'Small peanuts', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.8888E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':ce563bb') +Article.create!(name: 'Medjoul dates', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':8232061') +Article.create!(name: 'Turkish apricots natural', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':185084f') +Article.create!(name: 'Turkish apricots sulfurised', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':2b2fb20') +Article.create!(name: 'Spanish Figs', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.444E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':82590b1') +Article.create!(name: 'Turkish Figs', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.555E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':cabeeb6') +Article.create!(name: 'Sour Apricots South-Africa', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':2ac18b7') +Article.create!(name: 'Blue raisins Flames', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':16bfa75') +Article.create!(name: 'Yellow Raisins', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.2222E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':1c59324') +Article.create!(name: 'Red Raisins', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':c3fcd84') +Article.create!(name: 'Cranberries whole', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.222E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':921c168') +Article.create!(name: 'Dried apples', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.555E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':902c67b') +Article.create!(name: 'Dried plums without core', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.222E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':a847f91') +Article.create!(name: 'Pumpkin seeds', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.111E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':535645f') +Article.create!(name: 'Sunflower seeds', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.666E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':4ab9a83') +Article.create!(name: 'Linseed', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55E0, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':04be223') +Article.create!(name: 'Poppy seeds', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.7777E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':ec5b2b9') +Article.create!(name: 'Pine nuts medium china', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.2222E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':0e5b0b8') +Article.create!(name: 'Goji berries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':d52ee00') +Article.create!(name: 'Mulberries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.5555E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':5f46bd5') +Article.create!(name: 'Peeled Hemp', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.5555E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':c39165b') +Article.create!(name: 'Incaberries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':8d44fe7') +Article.create!(name: 'Blueberries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.2222E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':9a95422') +Article.create!(name: 'Chia seeds', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55555E3, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':416d57b') +Article.create!(name: 'Coconut grated', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55E0, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':b3f65e4') ## Members & groups -User.create!(:id => 1, :nick => "admin", :password => "secret", :first_name => "Anton", :last_name => "Administrator", :email => "admin@foo.test", :created_on => 'Wed, 15 Jan 2014 16:15:33 UTC +00:00') -User.create!(:id => 2, :nick => "john", :password => "secret", :first_name => "John", :last_name => "Doe", :email => "john@doe.test", :created_on => 'Sun, 19 Jan 2014 17:38:22 UTC +00:00') -User.create!(:id => 3, :nick => "peter", :password => "secret", :first_name => "Peter", :last_name => "Peters", :email => "peter@peters.test", :created_on => 'Sat, 25 Jan 2014 20:20:36 UTC +00:00') -User.create!(:id => 4, :nick => "jan", :password => "secret", :first_name => "Jan", :last_name => "Lou", :email => "jan@lou.test", :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00') -User.create!(:id => 5, :nick => "mary", :password => "secret", :first_name => "Mary", :last_name => "Lou", :email => "marie@lou.test", :created_on => 'Mon, 03 Feb 2014 11:47:17 UTC +00:00') - -Workgroup.create!(:id => 1, :name => "Administrators", :description => "System administrators.", :account_balance => 0.0, :created_on => 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', :role_admin => true, :role_suppliers => true, :role_article_meta => true, :role_finance => true, :role_orders => true, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Workgroup.create!(:id => 2, :name => "Finances", :account_balance => 0.0, :created_on => 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => true, :role_orders => false, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Workgroup.create!(:id => 3, :name => "Ordering", :account_balance => 0.0, :created_on => 'Thu, 20 Feb 2014 14:44:47 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => true, :role_finance => false, :role_orders => true, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Workgroup.create!(:id => 4, :name => "Assortment", :account_balance => 0.0, :created_on => 'Wed, 09 Apr 2014 12:24:55 UTC +00:00', :role_admin => false, :role_suppliers => true, :role_article_meta => true, :role_finance => false, :role_orders => false, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Ordergroup.create!(:id => 5, :name => "Admin Administrator", :account_balance => 0.0, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :stats => { :jobs_size => 0, :orders_sum => 1021.74 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => true) -Ordergroup.create!(:id => 6, :name => "Pete's house", :account_balance => -0.35E2, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "Piet Pieterssen", :stats => { :jobs_size => 0, :orders_sum => 60.96 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Ordergroup.create!(:id => 7, :name => "Jan Klaassen", :account_balance => -0.35E2, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "Jan Klaassen", :stats => { :jobs_size => 0, :orders_sum => 0 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Ordergroup.create!(:id => 8, :name => "John Doe", :account_balance => 0.90E2, :created_on => 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "John Doe", :stats => { :jobs_size => 0, :orders_sum => 0 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) - -Membership.create!(:group_id => 1, :user_id => 1) -Membership.create!(:group_id => 5, :user_id => 1) -Membership.create!(:group_id => 2, :user_id => 2) -Membership.create!(:group_id => 8, :user_id => 2) -Membership.create!(:group_id => 6, :user_id => 3) -Membership.create!(:group_id => 7, :user_id => 4) -Membership.create!(:group_id => 8, :user_id => 4) -Membership.create!(:group_id => 3, :user_id => 4) -Membership.create!(:group_id => 7, :user_id => 5) -Membership.create!(:group_id => 3, :user_id => 5) -Membership.create!(:group_id => 4, :user_id => 5) +User.create!(id: 1, nick: 'admin', password: 'secret', first_name: 'Anton', last_name: 'Administrator', + email: 'admin@foo.test', created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00') +User.create!(id: 2, nick: 'john', password: 'secret', first_name: 'John', last_name: 'Doe', email: 'john@doe.test', + created_on: 'Sun, 19 Jan 2014 17:38:22 UTC +00:00') +User.create!(id: 3, nick: 'peter', password: 'secret', first_name: 'Peter', last_name: 'Peters', + email: 'peter@peters.test', created_on: 'Sat, 25 Jan 2014 20:20:36 UTC +00:00') +User.create!(id: 4, nick: 'jan', password: 'secret', first_name: 'Jan', last_name: 'Lou', email: 'jan@lou.test', + created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00') +User.create!(id: 5, nick: 'mary', password: 'secret', first_name: 'Mary', last_name: 'Lou', email: 'marie@lou.test', + created_on: 'Mon, 03 Feb 2014 11:47:17 UTC +00:00') + +Workgroup.create!(id: 1, name: 'Administrators', description: 'System administrators.', account_balance: 0.0, created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', role_admin: true, role_suppliers: true, role_article_meta: true, role_finance: true, role_orders: true, + next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 2, name: 'Finances', account_balance: 0.0, created_on: 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: true, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 3, name: 'Ordering', account_balance: 0.0, created_on: 'Thu, 20 Feb 2014 14:44:47 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: true, role_finance: false, role_orders: true, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 4, name: 'Assortment', account_balance: 0.0, created_on: 'Wed, 09 Apr 2014 12:24:55 UTC +00:00', + role_admin: false, role_suppliers: true, role_article_meta: true, role_finance: false, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 5, name: 'Admin Administrator', account_balance: 0.0, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, stats: { jobs_size: 0, orders_sum: 1021.74 }, + next_weekly_tasks_number: 8, ignore_apple_restriction: true) +Ordergroup.create!(id: 6, name: "Pete's house", account_balance: -0.35E2, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Piet Pieterssen', + stats: { jobs_size: 0, orders_sum: 60.96 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 7, name: 'Jan Klaassen', account_balance: -0.35E2, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Jan Klaassen', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 8, name: 'John Doe', account_balance: 0.90E2, created_on: 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'John Doe', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) + +Membership.create!(group_id: 1, user_id: 1) +Membership.create!(group_id: 5, user_id: 1) +Membership.create!(group_id: 2, user_id: 2) +Membership.create!(group_id: 8, user_id: 2) +Membership.create!(group_id: 6, user_id: 3) +Membership.create!(group_id: 7, user_id: 4) +Membership.create!(group_id: 8, user_id: 4) +Membership.create!(group_id: 3, user_id: 4) +Membership.create!(group_id: 7, user_id: 5) +Membership.create!(group_id: 3, user_id: 5) +Membership.create!(group_id: 4, user_id: 5) ## Orders & OrderArticles @@ -181,10 +307,15 @@ ## Finances -FinancialTransactionType.create!(:id => 1, :name => "Foodcoop", :financial_transaction_class_id => 1) +FinancialTransactionType.create!(id: 1, name: 'Foodcoop', financial_transaction_class_id: 1) -FinancialTransaction.create!(:id => 1, :ordergroup_id => 5, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 1, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 3, :ordergroup_id => 6, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 1, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 4, :ordergroup_id => 7, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 1, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 5, :ordergroup_id => 5, :amount => 0.35E2, :note => "payment", :user_id => 2, :created_on => 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 6, :ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', :financial_transaction_type_id => 1) +FinancialTransaction.create!(id: 1, ordergroup_id: 5, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 3, ordergroup_id: 6, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 4, ordergroup_id: 7, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 5, ordergroup_id: 5, amount: 0.35E2, note: 'payment', user_id: 2, + created_on: 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 6, ordergroup_id: 8, amount: 0.90E2, note: 'Bank transfer', user_id: 2, + created_on: 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', financial_transaction_type_id: 1) diff --git a/db/seeds/small.nl.seeds.rb b/db/seeds/small.nl.seeds.rb index afa9cc04..9912eaf1 100644 --- a/db/seeds/small.nl.seeds.rb +++ b/db/seeds/small.nl.seeds.rb @@ -1,173 +1,299 @@ -require_relative 'seed_helper.rb' +require_relative 'seed_helper' ## Financial transaction classes -FinancialTransactionClass.create!(:id => 1, :name => 'Standaard') -FinancialTransactionClass.create!(:id => 2, :name => 'Foodsoft') +FinancialTransactionClass.create!(id: 1, name: 'Standaard') +FinancialTransactionClass.create!(id: 2, name: 'Foodsoft') ## Suppliers & articles -SupplierCategory.create!(:id => 1, :name => "Other", :financial_transaction_class_id => 1) +SupplierCategory.create!(id: 1, name: 'Other', financial_transaction_class_id: 1) Supplier.create!([ - { :id => 1, :name => "Koekenbakker", :supplier_category_id => 1, :address => "Dorpsstraat 1, Koekange", :phone => "012 3456789", :email => "info@dekoekenbakker.test", :min_order_quantity => "100" }, - { :id => 2, :name => "Chocolademakkers", :supplier_category_id => 1, :address => "Multatuliweg 1, Amsterdam", :phone => "012 3456789", :email => "info@chocolademakkers.test", :url => "http://www.chocolademakkers.test/", :contact_person => "Max Puur", :delivery_days => "di, vr (Amsterdam)" }, - { :id => 3, :name => "Kaasmaker", :supplier_category_id => 1, :address => "Waagplein, Alkmaar", :phone => "012 3456789", :url => "http://www.kaaskamer.test/" }, - { :id => 4, :name => "Notenhuis", :supplier_category_id => 1, :address => "Damrak 1, Amsterdam", :phone => "012 3456789", :email => "info@notenhuis.test", :url => "http://www.notenhuis.test/", :note => "leveren in Amsterdam; €9 leverkosten bij bestellingen onder €123" } + { id: 1, name: 'Koekenbakker', supplier_category_id: 1, address: 'Dorpsstraat 1, Koekange', + phone: '012 3456789', email: 'info@dekoekenbakker.test', min_order_quantity: '100' }, + { id: 2, name: 'Chocolademakkers', supplier_category_id: 1, address: 'Multatuliweg 1, Amsterdam', + phone: '012 3456789', email: 'info@chocolademakkers.test', url: 'http://www.chocolademakkers.test/', contact_person: 'Max Puur', delivery_days: 'di, vr (Amsterdam)' }, + { id: 3, name: 'Kaasmaker', supplier_category_id: 1, address: 'Waagplein, Alkmaar', + phone: '012 3456789', url: 'http://www.kaaskamer.test/' }, + { id: 4, name: 'Notenhuis', supplier_category_id: 1, address: 'Damrak 1, Amsterdam', + phone: '012 3456789', email: 'info@notenhuis.test', url: 'http://www.notenhuis.test/', note: 'leveren in Amsterdam; €9 leverkosten bij bestellingen onder €123' } ]) -ArticleCategory.create!(:id => 1, :name => "Other", :description => "overig, anders, onbekend") -ArticleCategory.create!(:id => 2, :name => "Fruit") -ArticleCategory.create!(:id => 3, :name => "Groenten") -ArticleCategory.create!(:id => 4, :name => "Aardappels & uien") -ArticleCategory.create!(:id => 5, :name => "Brood & Bakkerij") -ArticleCategory.create!(:id => 6, :name => "Dranken", :description => "sap, fruitsap, groentesap, frisdrank") -ArticleCategory.create!(:id => 7, :name => "Kruiden", :description => "kruiden, specerijen, conserveringsmiddelen, extracten") -ArticleCategory.create!(:id => 8, :name => "Zuivel", :description => "melk, boter, room, yoghurt, kaas, eieren, zuivelvervangers") -ArticleCategory.create!(:id => 9, :name => "Vis & Zee", :description => "vis, schaaldieren, schelpdieren") -ArticleCategory.create!(:id => 10, :name => "Vlees & Gevogelte") -ArticleCategory.create!(:id => 11, :name => "Oliën & Vetten") -ArticleCategory.create!(:id => 12, :name => "Graan & Peulvruchten") -ArticleCategory.create!(:id => 13, :name => "Noten & Zaden") -ArticleCategory.create!(:id => 14, :name => "Zoetwaren & Zoetstof") - -Article.create!(:name => "Volkoren heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Volkoren half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Volkoren sesam heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Volkoren sesam half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Licht tarwe heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Licht tarwe half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Zonnebloempitbrood heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Zonnebloempitbrood half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Walnoten vloer heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Walnoten vloer half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Kennemerlandbrood heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Kennemerlandbrood half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Maisbrood heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Maisbrood half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 1200 gram heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 1200 gram half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 900 gram heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Oberlander 900 gram half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Speltbrood heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Speltbrood half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Landbrood 900gram heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Landbrood 900gram half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Wit heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Wit half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Wit met maanzaad heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Wit met maanzaad half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Vijgenbrood heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Vijgenbrood half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bierborstelbrood heel", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.33E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bierborstelbrood half", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Krentenbol", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.99E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Mueslibol", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Brioche", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.91E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Volkoren croissant", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Croissants", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Kaas croissants", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocoladecroissants", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Soepstengels wit", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Soepstengels volkoren", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.99E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Pompoenpitten broodjes", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.88E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Witte kadetjes", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.66E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Bruine kadetjes", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.66E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Tomaten feta broodje", :supplier_id => 1, :article_category_id => 5, :unit => "stuk", :note => "bio", :availability => true, :manufacturer => "De Bakker", :origin => "NL", :price => 0.11E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocoladereep Melk (37%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "bio", :availability => true, :manufacturer => "Chocolademakkers", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocoladereep Puur (68%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "bio", :availability => true, :manufacturer => "Chocolademakkers", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocoladereep Drie Mensen Melk (40%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "bio", :availability => true, :manufacturer => "Chocolademakkers", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocoladereep Drie Mensen Puur (75%)", :supplier_id => 2, :article_category_id => 14, :unit => "90gr", :note => "bio", :availability => true, :manufacturer => "Chocolademakkers", :origin => "NL", :price => 0.22E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Chocoladereep Zwaan Puur (75%)", :supplier_id => 2, :article_category_id => 14, :unit => "120gr", :note => "bio", :availability => true, :manufacturer => "Chocolademakkers", :origin => "NL", :price => 0.66E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Cacao nibs", :supplier_id => 2, :article_category_id => 14, :unit => "1 kg", :note => "bio", :availability => true, :manufacturer => "Chocolademakkers", :origin => "NL", :price => 0.10E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1) -Article.create!(:name => "Kaas Koe-jong", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.88E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas koe- jong belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.99E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas koe- belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 12) -Article.create!(:name => "Kaas koe- extra belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "kaas Koe- oud", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.1375E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "kaas koe -overjarig", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas Koe-brandnetel jong", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.99E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas koe- brandnetel jong belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas koe- brandnetel belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas Koe-komijn jong", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.99E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas koe- komijn jong belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Kaas koe- komijn belegen", :supplier_id => 3, :article_category_id => 8, :unit => "kg", :note => "bio", :availability => true, :manufacturer => "Kaasboerderij", :origin => "NL", :price => 0.11E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 8) -Article.create!(:name => "Cashewnoten", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.4444E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 22, :order_number => ":b936051") -Article.create!(:name => "Hazel wit", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":9e3f85b") -Article.create!(:name => "Hazel bruin", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":d278041") -Article.create!(:name => "Amandel Bruin Spaans", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.999E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":0b51a8d") -Article.create!(:name => "Paranoten (bio)", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.6666E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 20, :order_number => ":01e59e3") -Article.create!(:name => "Bio walnoten light halfjes", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.333E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":7ff8587") -Article.create!(:name => "Pijnboompitten", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 25, :order_number => ":aa88d9f") -Article.create!(:name => "Pompoen", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 25, :order_number => ":e63069b") -Article.create!(:name => "Zonnepitten (bio)", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.999E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 25, :order_number => ":0428388") -Article.create!(:name => "Amandel Wit Spaans", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :note => "bio", :availability => true, :price => 0.66666E3, :tax => 6.0, :deposit => 0.0, :unit_quantity => 10, :order_number => ":a8f0734") -Article.create!(:name => "Cashew", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.6666E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":1d26958") -Article.create!(:name => "Amandelen geblancheerd", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.333E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":31439e2") -Article.create!(:name => "Amandelen naturel", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":9c49374") -Article.create!(:name => "Walnoot ELH hafjes", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.4444E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":92907d1") -Article.create!(:name => "Walnoot ELP stukjes", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.8888E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":395640e") -Article.create!(:name => "Paranoten", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.8888E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":710acbb") -Article.create!(:name => "Macadamia Stijl 0", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":bbaf40b") -Article.create!(:name => "Pecan", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55555E3, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":7958183") -Article.create!(:name => "Hazelnoten naturel", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.6666E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":50392a8") -Article.create!(:name => "Hazelnoten geblancheerd", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":4fe6525") -Article.create!(:name => "Gemengde Noten", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.333E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":c051b22") -Article.create!(:name => "Pinda's", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.777E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":f507577") -Article.create!(:name => "Vliespinda's klein", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.8888E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":ce563bb") -Article.create!(:name => "Medjoul dadels", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.3333E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":8232061") -Article.create!(:name => "Turkse Abrikozen ongezwaveld", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":185084f") -Article.create!(:name => "Turkse Abrikozen gezwaveld", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":2b2fb20") -Article.create!(:name => "Spaanse Vijgen", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.444E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":82590b1") -Article.create!(:name => "Turkse Vijgen", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.555E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":cabeeb6") -Article.create!(:name => "Zure Abrikozen Zuid Afrika", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":2ac18b7") -Article.create!(:name => "Blauwe rozijnen Flames", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":16bfa75") -Article.create!(:name => "Gele Rozijnen", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.2222E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":1c59324") -Article.create!(:name => "Rode Rozijnen", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.1111E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":c3fcd84") -Article.create!(:name => "Cranberries heel", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.222E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":921c168") -Article.create!(:name => "Gedroogde Appeltjes", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.555E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":902c67b") -Article.create!(:name => "Gedroogde pruimen zonder pit", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.222E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":a847f91") -Article.create!(:name => "Pompoenpitten", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.111E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":535645f") -Article.create!(:name => "Zonnenbloepitten", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.666E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":4ab9a83") -Article.create!(:name => "Lijnzaad", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":04be223") -Article.create!(:name => "Maanzaad", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.7777E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":ec5b2b9") -Article.create!(:name => "Pijnboompitten medium china", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.2222E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":0e5b0b8") -Article.create!(:name => "Goji bessen", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":d52ee00") -Article.create!(:name => "Mulberries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.5555E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":5f46bd5") -Article.create!(:name => "Gepelde Hennep", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.5555E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":c39165b") -Article.create!(:name => "Incaberries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.888E1, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":8d44fe7") -Article.create!(:name => "Blueberries", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.2222E2, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":9a95422") -Article.create!(:name => "Chia zaad", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55555E3, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":416d57b") -Article.create!(:name => "Cocos Rasp", :supplier_id => 4, :article_category_id => 13, :unit => "kg", :availability => true, :price => 0.55E0, :tax => 6.0, :deposit => 0.0, :unit_quantity => 1, :order_number => ":b3f65e4") +ArticleCategory.create!(id: 1, name: 'Other', description: 'overig, anders, onbekend') +ArticleCategory.create!(id: 2, name: 'Fruit') +ArticleCategory.create!(id: 3, name: 'Groenten') +ArticleCategory.create!(id: 4, name: 'Aardappels & uien') +ArticleCategory.create!(id: 5, name: 'Brood & Bakkerij') +ArticleCategory.create!(id: 6, name: 'Dranken', description: 'sap, fruitsap, groentesap, frisdrank') +ArticleCategory.create!(id: 7, name: 'Kruiden', description: 'kruiden, specerijen, conserveringsmiddelen, extracten') +ArticleCategory.create!(id: 8, name: 'Zuivel', + description: 'melk, boter, room, yoghurt, kaas, eieren, zuivelvervangers') +ArticleCategory.create!(id: 9, name: 'Vis & Zee', description: 'vis, schaaldieren, schelpdieren') +ArticleCategory.create!(id: 10, name: 'Vlees & Gevogelte') +ArticleCategory.create!(id: 11, name: 'Oliën & Vetten') +ArticleCategory.create!(id: 12, name: 'Graan & Peulvruchten') +ArticleCategory.create!(id: 13, name: 'Noten & Zaden') +ArticleCategory.create!(id: 14, name: 'Zoetwaren & Zoetstof') + +Article.create!(name: 'Volkoren heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Volkoren half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Volkoren sesam heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Volkoren sesam half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Licht tarwe heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Licht tarwe half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Zonnebloempitbrood heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Zonnebloempitbrood half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Walnoten vloer heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Walnoten vloer half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Kennemerlandbrood heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Kennemerlandbrood half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Maisbrood heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Maisbrood half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 1200 gram heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 1200 gram half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 900 gram heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Oberlander 900 gram half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Speltbrood heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Speltbrood half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Landbrood 900gram heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Landbrood 900gram half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Wit heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Wit half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Wit met maanzaad heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Wit met maanzaad half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Vijgenbrood heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Vijgenbrood half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bierborstelbrood heel', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.33E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bierborstelbrood half', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Krentenbol', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.99E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Mueslibol', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Brioche', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.91E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Volkoren croissant', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Croissants', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Kaas croissants', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocoladecroissants', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Soepstengels wit', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Soepstengels volkoren', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.99E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Pompoenpitten broodjes', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.88E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Witte kadetjes', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.66E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Bruine kadetjes', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.66E0, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Tomaten feta broodje', supplier_id: 1, article_category_id: 5, unit: 'stuk', note: 'bio', availability: true, manufacturer: 'De Bakker', origin: 'NL', price: 0.11E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocoladereep Melk (37%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'bio', availability: true, manufacturer: 'Chocolademakkers', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocoladereep Puur (68%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'bio', availability: true, manufacturer: 'Chocolademakkers', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocoladereep Drie Mensen Melk (40%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'bio', availability: true, manufacturer: 'Chocolademakkers', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocoladereep Drie Mensen Puur (75%)', supplier_id: 2, article_category_id: 14, unit: '90gr', note: 'bio', availability: true, manufacturer: 'Chocolademakkers', origin: 'NL', price: 0.22E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Chocoladereep Zwaan Puur (75%)', supplier_id: 2, article_category_id: 14, unit: '120gr', note: 'bio', availability: true, manufacturer: 'Chocolademakkers', origin: 'NL', price: 0.66E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Cacao nibs', supplier_id: 2, article_category_id: 14, unit: '1 kg', note: 'bio', availability: true, manufacturer: 'Chocolademakkers', origin: 'NL', price: 0.10E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })]) +Article.create!(name: 'Kaas Koe-jong', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.88E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- jong belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.99E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 12, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- extra belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'kaas Koe- oud', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.1375E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'kaas koe -overjarig', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas Koe-brandnetel jong', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.99E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- brandnetel jong belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- brandnetel belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas Koe-komijn jong', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.99E1, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- komijn jong belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Kaas koe- komijn belegen', supplier_id: 3, article_category_id: 8, unit: 'kg', note: 'bio', availability: true, manufacturer: 'Kaasboerderij', origin: 'NL', price: 0.11E2, tax: 6.0, deposit: 0.0, + article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 8, unit: 'XPP' })]) +Article.create!(name: 'Cashewnoten', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', + availability: true, price: 0.4444E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 22, unit: 'XPP' })], order_number: ':b936051') +Article.create!(name: 'Hazel wit', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', + availability: true, price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], order_number: ':9e3f85b') +Article.create!(name: 'Hazel bruin', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], order_number: ':d278041') +Article.create!(name: 'Amandel Bruin Spaans', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', availability: true, price: 0.999E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], + order_number: ':0b51a8d') +Article.create!(name: 'Paranoten (bio)', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', + availability: true, price: 0.6666E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 20, unit: 'XPP' })], order_number: ':01e59e3') +Article.create!(name: 'Bio walnoten light halfjes', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', availability: true, price: 0.333E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], + order_number: ':7ff8587') +Article.create!(name: 'Pijnboompitten', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', + availability: true, price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'XPP' })], order_number: ':aa88d9f') +Article.create!(name: 'Pompoen', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', availability: true, + price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'XPP' })], order_number: ':e63069b') +Article.create!(name: 'Zonnepitten (bio)', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', availability: true, price: 0.999E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 25, unit: 'XPP' })], + order_number: ':0428388') +Article.create!(name: 'Amandel Wit Spaans', supplier_id: 4, article_category_id: 13, unit: 'kg', note: 'bio', availability: true, price: 0.66666E3, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 10, unit: 'XPP' })], + order_number: ':a8f0734') +Article.create!(name: 'Cashew', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.6666E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':1d26958') +Article.create!(name: 'Amandelen geblancheerd', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.333E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':31439e2') +Article.create!(name: 'Amandelen naturel', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':9c49374') +Article.create!(name: 'Walnoot ELH hafjes', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.4444E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':92907d1') +Article.create!(name: 'Walnoot ELP stukjes', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.8888E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':395640e') +Article.create!(name: 'Paranoten', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.8888E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':710acbb') +Article.create!(name: 'Macadamia Stijl 0', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':bbaf40b') +Article.create!(name: 'Pecan', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55555E3, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':7958183') +Article.create!(name: 'Hazelnoten naturel', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.6666E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':50392a8') +Article.create!(name: 'Hazelnoten geblancheerd', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':4fe6525') +Article.create!(name: 'Gemengde Noten', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.333E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':c051b22') +Article.create!(name: "Pinda's", supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.777E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':f507577') +Article.create!(name: "Vliespinda's klein", supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.8888E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':ce563bb') +Article.create!(name: 'Medjoul dadels', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.3333E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':8232061') +Article.create!(name: 'Turkse Abrikozen ongezwaveld', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':185084f') +Article.create!(name: 'Turkse Abrikozen gezwaveld', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':2b2fb20') +Article.create!(name: 'Spaanse Vijgen', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.444E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':82590b1') +Article.create!(name: 'Turkse Vijgen', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.555E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':cabeeb6') +Article.create!(name: 'Zure Abrikozen Zuid Afrika', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':2ac18b7') +Article.create!(name: 'Blauwe rozijnen Flames', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':16bfa75') +Article.create!(name: 'Gele Rozijnen', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.2222E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':1c59324') +Article.create!(name: 'Rode Rozijnen', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.1111E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':c3fcd84') +Article.create!(name: 'Cranberries heel', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.222E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':921c168') +Article.create!(name: 'Gedroogde Appeltjes', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.555E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':902c67b') +Article.create!(name: 'Gedroogde pruimen zonder pit', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.222E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':a847f91') +Article.create!(name: 'Pompoenpitten', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.111E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':535645f') +Article.create!(name: 'Zonnenbloepitten', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.666E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':4ab9a83') +Article.create!(name: 'Lijnzaad', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55E0, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':04be223') +Article.create!(name: 'Maanzaad', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.7777E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':ec5b2b9') +Article.create!(name: 'Pijnboompitten medium china', supplier_id: 4, article_category_id: 13, unit: 'kg', + availability: true, price: 0.2222E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':0e5b0b8') +Article.create!(name: 'Goji bessen', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':d52ee00') +Article.create!(name: 'Mulberries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.5555E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':5f46bd5') +Article.create!(name: 'Gepelde Hennep', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.5555E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':c39165b') +Article.create!(name: 'Incaberries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.888E1, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':8d44fe7') +Article.create!(name: 'Blueberries', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.2222E2, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':9a95422') +Article.create!(name: 'Chia zaad', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55555E3, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':416d57b') +Article.create!(name: 'Cocos Rasp', supplier_id: 4, article_category_id: 13, unit: 'kg', availability: true, + price: 0.55E0, tax: 6.0, deposit: 0.0, article_unit_ratios: [ArticleUnitRatio.new({ sort: 1, quantity: 1, unit: 'XPP' })], order_number: ':b3f65e4') ## Members & groups -User.create!(:id => 1, :nick => "admin", :password => "secret", :first_name => "Anton", :last_name => "Administrator", :email => "admin@foo.test", :created_on => 'Wed, 15 Jan 2014 16:15:33 UTC +00:00') -User.create!(:id => 2, :nick => "john", :password => "secret", :first_name => "John", :last_name => "Doe", :email => "john@doe.test", :created_on => 'Sun, 19 Jan 2014 17:38:22 UTC +00:00') -User.create!(:id => 3, :nick => "peter", :password => "secret", :first_name => "Peter", :last_name => "Pieterssen", :email => "peter@pieterssen.test", :created_on => 'Sat, 25 Jan 2014 20:20:36 UTC +00:00') -User.create!(:id => 4, :nick => "jan", :password => "secret", :first_name => "Jan", :last_name => "Klaassen", :email => "jan@klaassen.test", :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00') -User.create!(:id => 5, :nick => "mary", :password => "secret", :first_name => "Marie", :last_name => "Klaassen", :email => "mary@klaassen.test", :created_on => 'Mon, 03 Feb 2014 11:47:17 UTC +00:00') - -Workgroup.create!(:id => 1, :name => "Admins", :description => "Beheerders", :account_balance => 0.0, :created_on => 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', :role_admin => true, :role_suppliers => true, :role_article_meta => true, :role_finance => true, :role_orders => true, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Workgroup.create!(:id => 2, :name => "Financiën", :account_balance => 0.0, :created_on => 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => true, :role_orders => false, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Workgroup.create!(:id => 3, :name => "Bestellen", :account_balance => 0.0, :created_on => 'Thu, 20 Feb 2014 14:44:47 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => true, :role_finance => false, :role_orders => true, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Workgroup.create!(:id => 4, :name => "Assortiment", :account_balance => 0.0, :created_on => 'Wed, 09 Apr 2014 12:24:55 UTC +00:00', :role_admin => false, :role_suppliers => true, :role_article_meta => true, :role_finance => false, :role_orders => false, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Ordergroup.create!(:id => 5, :name => "Admin Administrator", :account_balance => 0.0, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :stats => { :jobs_size => 0, :orders_sum => 1021.74 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => true) -Ordergroup.create!(:id => 6, :name => "Peter's huis", :account_balance => -0.35E2, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "Piet Pieterssen", :stats => { :jobs_size => 0, :orders_sum => 60.96 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Ordergroup.create!(:id => 7, :name => "Jan Klaassen", :account_balance => -0.35E2, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "Jan Klaassen", :stats => { :jobs_size => 0, :orders_sum => 0 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) -Ordergroup.create!(:id => 8, :name => "John Doe", :account_balance => 0.90E2, :created_on => 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "John Doe", :stats => { :jobs_size => 0, :orders_sum => 0 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false) - -Membership.create!(:group_id => 1, :user_id => 1) -Membership.create!(:group_id => 5, :user_id => 1) -Membership.create!(:group_id => 2, :user_id => 2) -Membership.create!(:group_id => 8, :user_id => 2) -Membership.create!(:group_id => 6, :user_id => 3) -Membership.create!(:group_id => 7, :user_id => 4) -Membership.create!(:group_id => 8, :user_id => 4) -Membership.create!(:group_id => 3, :user_id => 4) -Membership.create!(:group_id => 7, :user_id => 5) -Membership.create!(:group_id => 3, :user_id => 5) -Membership.create!(:group_id => 4, :user_id => 5) +User.create!(id: 1, nick: 'admin', password: 'secret', first_name: 'Anton', last_name: 'Administrator', + email: 'admin@foo.test', created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00') +User.create!(id: 2, nick: 'john', password: 'secret', first_name: 'John', last_name: 'Doe', email: 'john@doe.test', + created_on: 'Sun, 19 Jan 2014 17:38:22 UTC +00:00') +User.create!(id: 3, nick: 'peter', password: 'secret', first_name: 'Peter', last_name: 'Pieterssen', + email: 'peter@pieterssen.test', created_on: 'Sat, 25 Jan 2014 20:20:36 UTC +00:00') +User.create!(id: 4, nick: 'jan', password: 'secret', first_name: 'Jan', last_name: 'Klaassen', + email: 'jan@klaassen.test', created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00') +User.create!(id: 5, nick: 'mary', password: 'secret', first_name: 'Marie', last_name: 'Klaassen', + email: 'mary@klaassen.test', created_on: 'Mon, 03 Feb 2014 11:47:17 UTC +00:00') + +Workgroup.create!(id: 1, name: 'Admins', description: 'Beheerders', account_balance: 0.0, created_on: 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', role_admin: true, role_suppliers: true, role_article_meta: true, role_finance: true, role_orders: true, next_weekly_tasks_number: 8, + ignore_apple_restriction: false) +Workgroup.create!(id: 2, name: 'Financiën', account_balance: 0.0, created_on: 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: true, role_orders: false, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 3, name: 'Bestellen', account_balance: 0.0, created_on: 'Thu, 20 Feb 2014 14:44:47 UTC +00:00', + role_admin: false, role_suppliers: false, role_article_meta: true, role_finance: false, role_orders: true, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Workgroup.create!(id: 4, name: 'Assortiment', account_balance: 0.0, created_on: 'Wed, 09 Apr 2014 12:24:55 UTC +00:00', role_admin: false, role_suppliers: true, role_article_meta: true, role_finance: false, role_orders: false, next_weekly_tasks_number: 8, + ignore_apple_restriction: false) +Ordergroup.create!(id: 5, name: 'Admin Administrator', account_balance: 0.0, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, stats: { jobs_size: 0, orders_sum: 1021.74 }, + next_weekly_tasks_number: 8, ignore_apple_restriction: true) +Ordergroup.create!(id: 6, name: "Peter's huis", account_balance: -0.35E2, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Piet Pieterssen', + stats: { jobs_size: 0, orders_sum: 60.96 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 7, name: 'Jan Klaassen', account_balance: -0.35E2, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'Jan Klaassen', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) +Ordergroup.create!(id: 8, name: 'John Doe', account_balance: 0.90E2, created_on: 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', role_admin: false, role_suppliers: false, role_article_meta: false, role_finance: false, role_orders: false, contact_person: 'John Doe', + stats: { jobs_size: 0, orders_sum: 0 }, next_weekly_tasks_number: 8, ignore_apple_restriction: false) + +Membership.create!(group_id: 1, user_id: 1) +Membership.create!(group_id: 5, user_id: 1) +Membership.create!(group_id: 2, user_id: 2) +Membership.create!(group_id: 8, user_id: 2) +Membership.create!(group_id: 6, user_id: 3) +Membership.create!(group_id: 7, user_id: 4) +Membership.create!(group_id: 8, user_id: 4) +Membership.create!(group_id: 3, user_id: 4) +Membership.create!(group_id: 7, user_id: 5) +Membership.create!(group_id: 3, user_id: 5) +Membership.create!(group_id: 4, user_id: 5) ## Orders & OrderArticles @@ -181,10 +307,15 @@ ## Finances -FinancialTransactionType.create!(:id => 1, :name => "Foodcoop", :financial_transaction_class_id => 1) +FinancialTransactionType.create!(id: 1, name: 'Foodcoop', financial_transaction_class_id: 1) -FinancialTransaction.create!(:id => 1, :ordergroup_id => 5, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 1, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 3, :ordergroup_id => 6, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 1, :created_on => 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 4, :ordergroup_id => 7, :amount => -0.35E2, :note => "Membership fee for ordergroup", :user_id => 1, :created_on => 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 5, :ordergroup_id => 5, :amount => 0.35E2, :note => "payment", :user_id => 2, :created_on => 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', :financial_transaction_type_id => 1) -FinancialTransaction.create!(:id => 6, :ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', :financial_transaction_type_id => 1) +FinancialTransaction.create!(id: 1, ordergroup_id: 5, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 3, ordergroup_id: 6, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Sat, 25 Jan 2014 20:20:37 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 4, ordergroup_id: 7, amount: -0.35E2, note: 'Membership fee for ordergroup', + user_id: 1, created_on: 'Mon, 27 Jan 2014 16:22:14 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 5, ordergroup_id: 5, amount: 0.35E2, note: 'payment', user_id: 2, + created_on: 'Wed, 05 Feb 2014 16:49:24 UTC +00:00', financial_transaction_type_id: 1) +FinancialTransaction.create!(id: 6, ordergroup_id: 8, amount: 0.90E2, note: 'Bank transfer', user_id: 2, + created_on: 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', financial_transaction_type_id: 1) diff --git a/db_shared/migrate/20221204112447_create_articles_and_suppliers.rb b/db_shared/migrate/20221204112447_create_articles_and_suppliers.rb new file mode 100644 index 00000000..f39909cd --- /dev/null +++ b/db_shared/migrate/20221204112447_create_articles_and_suppliers.rb @@ -0,0 +1,50 @@ +class CreateArticlesAndSuppliers < ActiveRecord::Migration[5.2] + def change + create_table 'articles', force: :cascade do |t| + t.string 'name', null: false + t.integer 'supplier_id', null: false + t.string 'number' + t.string 'note' + t.string 'manufacturer' + t.string 'origin' + t.string 'unit' + t.decimal 'price', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'tax', precision: 3, scale: 1, default: '7.0', null: false + t.decimal 'deposit', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'unit_quantity', precision: 4, scale: 1, default: '1.0', null: false + t.decimal 'scale_quantity', precision: 4, scale: 2 + t.decimal 'scale_price', precision: 8, scale: 2 + t.datetime 'created_on' + t.datetime 'updated_on' + t.string 'category' + t.index ['name'], name: 'index_articles_on_name' + t.index %w[number supplier_id], name: 'index_articles_on_number_and_supplier_id', unique: true + end + + create_table 'suppliers', force: :cascade do |t| + t.string 'name', null: false + t.string 'address', null: false + t.string 'phone', null: false + t.string 'phone2' + t.string 'fax' + t.string 'email' + t.string 'url' + t.string 'delivery_days' + t.string 'note' + t.datetime 'created_on' + t.datetime 'updated_on' + t.boolean 'ftp_sync', default: false + t.string 'ftp_host' + t.string 'ftp_user' + t.string 'ftp_password' + t.string 'ftp_type', default: 'bnn', null: false + t.string 'ftp_regexp', default: '^([.]/)?PL' + t.boolean 'mail_sync' + t.string 'mail_from' + t.string 'mail_subject' + t.string 'mail_type' + t.string 'salt', null: true + t.index ['name'], name: 'index_suppliers_on_name', unique: true + end + end +end diff --git a/db_shared/schema.rb b/db_shared/schema.rb new file mode 100644 index 00000000..19fbca91 --- /dev/null +++ b/db_shared/schema.rb @@ -0,0 +1,60 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20_221_204_112_447) do + create_table 'articles', options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4', force: :cascade do |t| + t.string 'name', null: false + t.integer 'supplier_id', null: false + t.string 'number' + t.string 'note' + t.string 'manufacturer' + t.string 'origin' + t.string 'unit' + t.decimal 'price', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'tax', precision: 3, scale: 1, default: '7.0', null: false + t.decimal 'deposit', precision: 8, scale: 2, default: '0.0', null: false + t.decimal 'unit_quantity', precision: 4, scale: 1, default: '1.0', null: false + t.decimal 'scale_quantity', precision: 4, scale: 2 + t.decimal 'scale_price', precision: 8, scale: 2 + t.datetime 'created_on' + t.datetime 'updated_on' + t.string 'category' + t.index ['name'], name: 'index_articles_on_name' + t.index %w[number supplier_id], name: 'index_articles_on_number_and_supplier_id', unique: true + end + + create_table 'suppliers', options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4', force: :cascade do |t| + t.string 'name', null: false + t.string 'address', null: false + t.string 'phone', null: false + t.string 'phone2' + t.string 'fax' + t.string 'email' + t.string 'url' + t.string 'delivery_days' + t.string 'note' + t.datetime 'created_on' + t.datetime 'updated_on' + t.boolean 'ftp_sync', default: false + t.string 'ftp_host' + t.string 'ftp_user' + t.string 'ftp_password' + t.string 'ftp_type', default: 'bnn', null: false + t.string 'ftp_regexp', default: '^([.]/)?PL' + t.boolean 'mail_sync' + t.string 'mail_from' + t.string 'mail_subject' + t.string 'mail_type' + t.string 'salt' + t.index ['name'], name: 'index_suppliers_on_name', unique: true + end +end diff --git a/db_shared/seeds.rb b/db_shared/seeds.rb new file mode 100644 index 00000000..e69de29b diff --git a/doc/SETUP_DEVELOPMENT_DOCKER.md b/doc/SETUP_DEVELOPMENT_DOCKER.md index 1fdf9da6..580e45a7 100644 --- a/doc/SETUP_DEVELOPMENT_DOCKER.md +++ b/doc/SETUP_DEVELOPMENT_DOCKER.md @@ -75,7 +75,9 @@ Open a rails console Setup the test database docker-compose-dev run --rm mariadb mariadb --host=mariadb --password=secret --execute="CREATE DATABASE test" + docker-compose-dev run --rm mariadb mariadb --host=mariadb --password=secret --execute="CREATE DATABASE db_shared" docker-compose-dev run --rm foodsoft bundle exec rake db:schema:load RAILS_ENV=test DATABASE_URL=mysql2://root:secret@mariadb/test?encoding=utf8mb4 + docker-compose-dev run --rm foodsoft bundle exec rake shared:db:schema:load RAILS_ENV=test DATABASE_URL=mysql2://root:secret@mariadb/db_shared?encoding=utf8mb4 Run the tests diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 0a8b3fec..43eeca7a 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -35,6 +35,8 @@ services: - MYSQL_DATABASE=development volumes: - mariadb:/var/lib/mysql + ports: + - "3306:3306" phpmyadmin: image: phpmyadmin/phpmyadmin diff --git a/lib/apple_bar.rb b/lib/apple_bar.rb index a2176ea3..3f81197d 100644 --- a/lib/apple_bar.rb +++ b/lib/apple_bar.rb @@ -14,13 +14,11 @@ def initialize(ordergroup) def group_bar_state if apples >= 100 'success' + elsif FoodsoftConfig[:stop_ordering_under].present? and + apples >= FoodsoftConfig[:stop_ordering_under] + 'warning' else - if FoodsoftConfig[:stop_ordering_under].present? and - apples >= FoodsoftConfig[:stop_ordering_under] - 'warning' - else - 'danger' - end + 'danger' end end @@ -30,7 +28,9 @@ def group_bar_width end def mean_order_amount_per_job - (1 / @global_avg).round rescue 0 + (1 / @global_avg).round + rescue StandardError + 0 end def apples diff --git a/lib/article_units_lib.rb b/lib/article_units_lib.rb new file mode 100644 index 00000000..b99aded5 --- /dev/null +++ b/lib/article_units_lib.rb @@ -0,0 +1,181 @@ +class ArticleUnitsLib + @un_ece_20_units = YAML.safe_load(ERB.new(File.read(File.expand_path( + 'config/units-of-measure/un-ece-20-remastered.yml', Rails.root + ))).result) + @un_ece_21_units = YAML.safe_load(ERB.new(File.read(File.expand_path('config/units-of-measure/un-ece-21.yml', + Rails.root))).result) + + def self.untranslated_units + return @untranslated_units unless @untranslated_units.nil? + + options = {} + + @un_ece_20_units.each do |unit| + code = unit['CommonCode'] + base_unit = unit['conversion']['base_units'].nil? ? nil : unit['conversion']['base_units'][0] + options[code] = + { name: unit['Name'], description: unit['Description'], baseUnit: base_unit, + conversionFactor: unit['conversion']['factor'], symbol: unit['Symbol'] } + end + + @un_ece_21_units.each do |unit| + code = 'X' + unit['Code'] + name = unit['Name'] + name[0] = name[0].downcase + + options[code] = + { name: name, description: unit['Description'], baseUnit: nil, conversionFactor: nil, symbol: unit['Symbol'] } + end + + options.each do |code, option| + option[:translation_available] = !ArticleUnitsLib.get_translated_name_for_code(code, default_nil: true).nil? + end + + @untranslated_units = options + end + + def self.unit_translations + return @unit_translations unless @unit_translations.nil? + + @unit_translations = YAML.safe_load(ERB.new(File.read(File.expand_path( + "config/units-of-measure/locales/unece_#{I18n.locale}.yml", Rails.root + ))).result) || {} + end + + def self.units + return @units unless @units.nil? + + units = untranslated_units + @units = units.to_h do |code, unit| + translated_name = ArticleUnitsLib.get_translated_name_for_code(code, default_nil: true) + unit = unit.clone + unit[:name] = translated_name || unit[:name] + unit[:untranslated] = translated_name.nil? + unit[:symbol] = ArticleUnitsLib.get_translated_symbol_for_code(code) + unit[:aliases] = ArticleUnitsLib.get_translated_aliases_for_code(code) + + [code, unit] + end + end + + def self.unit_is_si_convertible(code) + !units.to_h[code]&.dig(:conversionFactor).nil? + end + + def self.get_translated_name_for_code(code, default_nil: false) + return nil if code.blank? + + unit_translations&.dig('unece_units')&.dig(code)&.dig('name') || (default_nil ? nil : untranslated_units[code][:name]) + end + + def self.get_translated_symbol_for_code(code) + return nil if code.blank? + + unit_translations&.dig('unece_units')&.dig(code)&.dig('symbols')&.dig(0) || untranslated_units[code][:symbol] + end + + def self.get_translated_aliases_for_code(code) + return nil if code.blank? + + unit_translations&.dig('unece_units')&.dig(code)&.dig('aliases') + end + + def self.get_code_for_unit_name(name) + return nil if name.blank? + + translation = unit_translations&.dig('unece_units')&.find do |_code, translations| + translations['name'] == name + end + + return translation[0] unless translation.nil? + + matching_unit = units.find do |_code, unit| + unit[:name] == name + end + + matching_unit[0] + end + + def self.convert_old_unit(old_compound_unit_str, unit_quantity) + return nil if old_compound_unit_str.nil? + + md = old_compound_unit_str.match(/([0-9]*)x(.*)/) + old_compound_unit_str = md[2] if !md.nil? && md[1].to_f == unit_quantity + + md = old_compound_unit_str.match(%r{^\s*([0-9][0-9,./]*)?\s*([A-Za-z\u00C0-\u017F.]+)\s*$}) + return nil if md.nil? + + unit = get_unit_from_old_str(md[2]) + return nil if unit.nil? + + quantity = get_quantity_from_old_str(md[1]) + + if quantity == 1 && unit_quantity == 1 + { + supplier_order_unit: unit, + first_ratio: nil, + group_order_granularity: 1.0, + group_order_unit: unit + } + else + supplier_order_unit = unit.starts_with?('X') && unit != 'XPK' ? 'XPK' : 'XPP' + { + supplier_order_unit: supplier_order_unit, + first_ratio: { + quantity: quantity * unit_quantity, + unit: unit + }, + group_order_granularity: unit_quantity > 1 ? quantity : 1.0, + group_order_unit: unit_quantity > 1 ? unit : supplier_order_unit + } + end + end + + def self.get_quantity_from_old_str(quantity_str) + return 1 if quantity_str.nil? + + quantity_str = quantity_str + .gsub(',', '.') + .gsub(' ', '') + + division_parts = quantity_str.split('/').map(&:to_f) + + if division_parts.length == 2 + division_parts[0] / division_parts[1] + else + quantity_str.to_f + end + end + + def self.get_unit_from_old_str(old_unit_str) + unit_str = old_unit_str.strip.downcase + units = ArticleUnitsLib.untranslated_units + .sort { |a, b| sort_by_translation_state(a[1], b[1]) } + matching_unit_arr = units.select { |key, unit| matches_unit(key, unit, unit_str) } + .to_a + return nil if matching_unit_arr.empty? + + matching_unit_arr[0][0] + end + + def self.sort_by_translation_state(unit_a, unit_b) + return -1 if unit_a[:translation_available] && !unit_b[:translation_available] + return 1 if unit_b[:translation_available] && !unit_a[:translation_available] + + 0 + end + + def self.matches_unit(unit_code, unit, unit_str) + return true if unit[:symbol] == unit_str + + translation_data = unit_translations&.dig('unece_units')&.dig(unit_code) + + return true if translation_data&.dig('symbols')&.include?(unit_str) + + name = translation_data&.dig('name')&.downcase + return true if !name.nil? && name == unit_str + + aliases = translation_data&.dig('aliases')&.map(&:strip)&.map(&:downcase) + !aliases.nil? && aliases.any? { |a| a == unit_str || "#{a}." == unit_str } + end +end diff --git a/lib/articles_csv.rb b/lib/articles_csv.rb index 910de9be..8202547f 100644 --- a/lib/articles_csv.rb +++ b/lib/articles_csv.rb @@ -13,31 +13,45 @@ def header Article.human_attribute_name(:price), Article.human_attribute_name(:tax), Article.human_attribute_name(:deposit), - Article.human_attribute_name(:unit_quantity), - '', - '', + Article.human_attribute_name(:supplier_order_unit), + Article.human_attribute_name(:price_unit), + Article.human_attribute_name(:group_order_unit), + Article.human_attribute_name(:group_order_granularity), + Article.human_attribute_name(:minimum_order_quantity), + Article.human_attribute_name(:billing_unit), Article.human_attribute_name(:article_category), + Article.human_attribute_name(:ratios_to_supplier_order_unit) ] end def data - @object.each do |o| + @object.each do |article| yield [ '', - o.order_number, - o.name, - o.note, - o.manufacturer, - o.origin, - o.unit, - o.price, - o.tax, - o.deposit, - o.unit_quantity, - '', - '', - o.article_category.try(:name), + article.order_number, + article.name, + article.note, + article.manufacturer, + article.origin, + article.unit, + article.price, + article.tax, + article.deposit, + ArticleUnitsLib.get_translated_name_for_code(article.supplier_order_unit), + ArticleUnitsLib.get_translated_name_for_code(article.price_unit), + ArticleUnitsLib.get_translated_name_for_code(article.group_order_unit), + article.group_order_granularity, + article.minimum_order_quantity, + ArticleUnitsLib.get_translated_name_for_code(article.billing_unit), + article.article_category.try(:name), + article.article_unit_ratios.map do |ratio| + "#{ratio.quantity} #{escape_csv_ratio(ArticleUnitsLib.get_translated_name_for_code(ratio.unit))}" + end.join(', ') ] end end + + def escape_csv_ratio(str) + str.gsub('\\', '\\\\').gsub(',', '\\,') + end end diff --git a/lib/bank_account_connector.rb b/lib/bank_account_connector.rb index 93e7cc7c..b728ebb9 100644 --- a/lib/bank_account_connector.rb +++ b/lib/bank_account_connector.rb @@ -8,9 +8,7 @@ def name nil end - def text - @text - end + attr_reader :text end class TextField @@ -24,13 +22,7 @@ def type nil end - def name - @name - end - - def value - @value - end + attr_reader :name, :value def label @label || @name.to_s @@ -73,17 +65,7 @@ def iban @bank_account.iban end - def auto_submit - @auto_submit - end - - def controls - @controls - end - - def count - @count - end + attr_reader :auto_submit, :controls, :count def text(data) @controls += [TextItem.new(data)] @@ -142,11 +124,9 @@ def finish @bank_account.save! end - def load(data) - end + def load(data); end - def dump - end + def dump; end def t(key, args = {}) return t(".fields.#{key}") unless key.is_a? String diff --git a/lib/bank_account_information_importer.rb b/lib/bank_account_information_importer.rb index 517f91c2..8e6626ad 100644 --- a/lib/bank_account_information_importer.rb +++ b/lib/bank_account_information_importer.rb @@ -24,16 +24,16 @@ def import!(content) iban: entityAccount && entityAccount[:iban], reference: t[:remittanceInformationUnstructured], text: entityName, - receipt: t[:additionalInformation], + receipt: t[:additionalInformation] }) ret += 1 end balances = Hash[data[:balances] ? data[:balances].map { |b| [b[:balanceType], b[:balanceAmount]] } : []] balance = balances.values.first - %w(closingBooked expected authorised openingBooked interimAvailable forwardAvailable nonInvoiced).each do |type| + %w[closingBooked expected authorised openingBooked interimAvailable forwardAvailable nonInvoiced].each do |type| value = balances[type] - if value then + if value balance = value break end diff --git a/lib/bank_transaction_reference.rb b/lib/bank_transaction_reference.rb index d033c544..5e44f0ef 100644 --- a/lib/bank_transaction_reference.rb +++ b/lib/bank_transaction_reference.rb @@ -13,7 +13,7 @@ def self.parse(data) ret = { group: m[:group].to_i, parts: parts } ret[:user] = m[:user].to_i if m[:user] - return ret + ret end def self.js_code_for_user(user) diff --git a/lib/date_time_attribute_validate.rb b/lib/date_time_attribute_validate.rb index 08138d02..17b40d36 100644 --- a/lib/date_time_attribute_validate.rb +++ b/lib/date_time_attribute_validate.rb @@ -10,51 +10,67 @@ def date_time_attribute(*attributes) super attributes.each do |attribute| - validate -> { self.send("#{attribute}_datetime_value_valid") } + validate -> { send("#{attribute}_datetime_value_valid") } # allow resetting the field to nil before_validation do - if self.instance_variable_get("@#{attribute}_is_set") - date = self.instance_variable_get("@#{attribute}_date_value") - time = self.instance_variable_get("@#{attribute}_time_value") - if date.blank? && time.blank? - self.send("#{attribute}=", nil) - end + if instance_variable_get("@#{attribute}_is_set") + date = instance_variable_get("@#{attribute}_date_value") + time = instance_variable_get("@#{attribute}_time_value") + send("#{attribute}=", nil) if date.blank? && time.blank? end end # remember old date and time values define_method("#{attribute}_date_value=") do |val| - self.instance_variable_set("@#{attribute}_is_set", true) - self.instance_variable_set("@#{attribute}_date_value", val) - self.send("#{attribute}_date=", val) rescue nil + instance_variable_set("@#{attribute}_is_set", true) + instance_variable_set("@#{attribute}_date_value", val) + begin + send("#{attribute}_date=", val) + rescue StandardError + nil + end end define_method("#{attribute}_time_value=") do |val| - self.instance_variable_set("@#{attribute}_is_set", true) - self.instance_variable_set("@#{attribute}_time_value", val) - self.send("#{attribute}_time=", val) rescue nil + instance_variable_set("@#{attribute}_is_set", true) + instance_variable_set("@#{attribute}_time_value", val) + begin + send("#{attribute}_time=", val) + rescue StandardError + nil + end end # fallback to field when values are not set define_method("#{attribute}_date_value") do - self.instance_variable_get("@#{attribute}_date_value") || self.send("#{attribute}_date").try { |e| e.strftime('%Y-%m-%d') } + instance_variable_get("@#{attribute}_date_value") || send("#{attribute}_date").try do |e| + e.strftime('%Y-%m-%d') + end end define_method("#{attribute}_time_value") do - self.instance_variable_get("@#{attribute}_time_value") || self.send("#{attribute}_time").try { |e| e.strftime('%H:%M') } + instance_variable_get("@#{attribute}_time_value") || send("#{attribute}_time").try { |e| e.strftime('%H:%M') } end private # validate date and time define_method("#{attribute}_datetime_value_valid") do - date = self.instance_variable_get("@#{attribute}_date_value") - unless date.blank? || (Date.parse(date) rescue nil) - errors.add(attribute, "is not a valid date") # @todo I18n + date = instance_variable_get("@#{attribute}_date_value") + unless date.blank? || begin + Date.parse(date) + rescue StandardError + nil end - time = self.instance_variable_get("@#{attribute}_time_value") - unless time.blank? || (Time.parse(time) rescue nil) - errors.add(attribute, "is not a valid time") # @todo I18n + errors.add(attribute, 'is not a valid date') # @todo I18n end + time = instance_variable_get("@#{attribute}_time_value") + return if time.blank? || begin + Time.parse(time) + rescue StandardError + nil + end + + errors.add(attribute, 'is not a valid time') # @todo I18n end end end diff --git a/lib/foodsoft/expansion_variables.rb b/lib/foodsoft/expansion_variables.rb index bcf67e7a..5ee54acb 100644 --- a/lib/foodsoft/expansion_variables.rb +++ b/lib/foodsoft/expansion_variables.rb @@ -39,7 +39,7 @@ module ExpansionVariables 'supplier_count' => -> { Supplier.undeleted.count }, 'active_supplier_count' => -> { active_supplier_count }, 'active_suppliers' => -> { active_suppliers }, - 'first_order_date' => -> { I18n.l Order.first.try { |o| o.starts.to_date } } + 'first_order_date' => -> { I18n.l(Order.first.try { |o| o.starts.to_date }) } } # Return expanded variable @@ -54,8 +54,8 @@ def self.get(var) # @param options [Hash] Extra variables to expand # @return [String] Expanded string def self.expand(str, options = {}) - str.gsub /{{([._a-zA-Z0-9]+)}}/ do - options[$1] || self.get($1) + str.gsub(/{{([._a-zA-Z0-9]+)}}/) do + options[::Regexp.last_match(1)] || get(::Regexp.last_match(1)) end end diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 33fa9e45..3f6b893d 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -68,7 +68,7 @@ def init(filename = APP_CONFIG_FILE) # Load initial config from development or production set_config Rails.env # Overwrite scope to have a better namescope than 'production' - self.scope = config[:default_scope] or raise "No default_scope is set" + self.scope = config[:default_scope] or raise 'No default_scope is set' # Set defaults for backward-compatibility set_missing # Make sure relevant configuration is applied, also in single coops mode, @@ -77,7 +77,7 @@ def init(filename = APP_CONFIG_FILE) end def init_mailing - [:protocol, :host, :port, :script_name].each do |k| + %i[protocol host port script_name].each do |k| ActionMailer::Base.default_url_options[k] = self[k] if self[k] end end @@ -115,7 +115,7 @@ def select_multifoodcoop(foodcoop) # @return [Object] Value of the key. def [](key) if RailsSettings::CachedSettings.table_exists? && allowed_key?(key) - value = RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] + value = RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"] value = config[key] if value.nil? value else @@ -137,20 +137,19 @@ def []=(key, value) if config[key] == value || (config[key].nil? && value == false) # delete (ok if it was already deleted) begin - RailsSettings::CachedSettings.destroy "foodcoop.#{self.scope}.#{key}" + RailsSettings::CachedSettings.destroy "foodcoop.#{scope}.#{key}" rescue RailsSettings::Settings::SettingNotFound end else # or store - RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] = value + RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"] = value end - true end # @return [Array] Configuration keys that are set (either in +app_config.yml+ or database). def keys - keys = RailsSettings::CachedSettings.get_all("foodcoop.#{self.scope}.").try(:keys) || [] - keys.map! { |k| k.gsub(/^foodcoop\.#{self.scope}\./, '') } + keys = RailsSettings::CachedSettings.get_all("foodcoop.#{scope}.").try(:keys) || [] + keys.map! { |k| k.gsub(/^foodcoop\.#{scope}\./, '') } keys += config.keys keys.map(&:to_s).uniq end @@ -179,17 +178,17 @@ def allowed_foodcoop?(foodcoop) # @return [Boolean] Whether this key may be set in the database def allowed_key?(key) # fast check for keys without nesting - if self.config[:protected].include? key - !self.config[:protected][key] + if config[:protected].include? key + !config[:protected][key] else - !self.config[:protected][:all] + !config[:protected][:all] end # @todo allow to check nested keys as well end # @return [Hash] Full configuration. def to_hash - keys.map { |k| [k, self[k]] }.to_h + keys.index_with { |k| self[k] } end # for using active_model_serializer in the api/v1/configs controller @@ -286,7 +285,9 @@ def get_default_config def normalize_value(value) value = value.map { |v| normalize_value(v) } if value.is_a? Array if value.is_a? Hash - value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map { |a| [a[0], normalize_value(a[1])] }] + value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map do |a| + [a[0], normalize_value(a[1])] + end] end case value when 'true' then true diff --git a/lib/foodsoft_date_util.rb b/lib/foodsoft_date_util.rb index 98dc1c61..b1bc7430 100644 --- a/lib/foodsoft_date_util.rb +++ b/lib/foodsoft_date_util.rb @@ -6,11 +6,13 @@ def self.next_occurrence(start = Time.now, from = start, options = {}) schedule = IceCube::Schedule.new(start) schedule.add_recurrence_rule rule_from(options[:recurr]) # @todo handle ical parse errors - occ = (schedule.next_occurrence(from).to_time rescue nil) - end - if options && options[:time] && occ - occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight) + occ = begin + schedule.next_occurrence(from).to_time + rescue StandardError + nil + end end + occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight) if options && options[:time] && occ occ end diff --git a/lib/foodsoft_file.rb b/lib/foodsoft_file.rb index 95d06c60..4276fcea 100644 --- a/lib/foodsoft_file.rb +++ b/lib/foodsoft_file.rb @@ -4,22 +4,47 @@ class FoodsoftFile # returns two arrays with articles and outlisted_articles # the parsed article is a simple hash def self.parse(file, options = {}) - SpreadsheetFile.parse file, options do |row, row_index| + articles = [] + SpreadsheetFile.parse file, options do |row| next if row[2].blank? - article = { :order_number => row[1], - :name => row[2], - :note => row[3], - :manufacturer => row[4], - :origin => row[5], - :unit => row[6], - :price => row[7], - :tax => row[8], - :deposit => (row[9].nil? ? "0" : row[9]), - :unit_quantity => row[10], - :article_category => row[13] } - status = row[0] && row[0].strip.downcase == 'x' ? :outlisted : nil - yield status, article, row_index + article = { availability: row[0]&.strip&.downcase != 'x', + order_number: row[1], + name: row[2], + note: row[3], + manufacturer: row[4], + origin: row[5], + unit: row[6], + price: row[7], + tax: row[8], + deposit: (row[9].nil? ? '0' : row[9]), + supplier_order_unit: ArticleUnitsLib.get_code_for_unit_name(row[10]), + price_unit: ArticleUnitsLib.get_code_for_unit_name(row[11]), + group_order_unit: ArticleUnitsLib.get_code_for_unit_name(row[12]), + group_order_granularity: row[13], + minimum_order_quantity: row[14], + billing_unit: ArticleUnitsLib.get_code_for_unit_name(row[15]), + article_category: row[16], + article_unit_ratios: FoodsoftFile.parse_ratios_cell(row[17]) } + articles << article end + + articles + end + + def self.parse_ratios_cell(ratios_cell) + return [] if ratios_cell.blank? + + ratios = ratios_cell.split(/(?[+-]?(?:[0-9]*[.])?[0-9]+) (?.*)/) + { + sort: index + 1, + quantity: md[:quantity], + unit: ArticleUnitsLib.get_code_for_unit_name(md[:unit_name]) + } + end + + ratios.reject { |ratio| ratio[:unit].nil? } end end diff --git a/lib/foodsoft_mail_receiver.rb b/lib/foodsoft_mail_receiver.rb index 560e7edd..7c5918a7 100644 --- a/lib/foodsoft_mail_receiver.rb +++ b/lib/foodsoft_mail_receiver.rb @@ -19,31 +19,29 @@ def start private - def on_rcpt_to_event(ctx, rcpt_to) + def on_rcpt_to_event(_ctx, rcpt_to) recipient = rcpt_to.gsub(/^\s*<\s*(.*)\s*>\s*$/, '\1') @handlers << self.class.find_handler(recipient) rcpt_to - rescue => error - logger.info("Can not accept mail for '#{rcpt_to}': #{error}") + rescue StandardError => e + logger.info("Can not accept mail for '#{rcpt_to}': #{e}") raise MidiSmtpServer::Smtpd550Exception end def on_message_data_event(ctx) - begin - @handlers.each do |handler| - handler.call(ctx[:message][:data]) - end - rescue => error - ExceptionNotifier.notify_exception(error, data: ctx) - raise error - ensure - @handlers.clear + @handlers.each do |handler| + handler.call(ctx[:message][:data]) end + rescue StandardError => e + ExceptionNotifier.notify_exception(e, data: ctx) + raise e + ensure + @handlers.clear end def self.find_handler(recipient) m = /(?[^@\.]+)\.(?
[^@]+)(@(?[^@]+))?/.match recipient - raise "recipient is missing or has an invalid format" if m.nil? + raise 'recipient is missing or has an invalid format' if m.nil? raise "Foodcoop '#{m[:foodcoop]}' could not be found" unless FoodsoftConfig.allowed_foodcoop? m[:foodcoop] FoodsoftConfig.select_multifoodcoop m[:foodcoop] @@ -51,10 +49,10 @@ def self.find_handler(recipient) @@registered_classes.each do |klass| if match = klass.regexp.match(m[:address]) handler = klass.new match - return lambda { |data| handler.received(data) } + return ->(data) { handler.received(data) } end end - raise "invalid format for recipient" + raise 'invalid format for recipient' end end diff --git a/lib/invoices_csv.rb b/lib/invoices_csv.rb index aa20cd08..ebd1f0a9 100644 --- a/lib/invoices_csv.rb +++ b/lib/invoices_csv.rb @@ -32,7 +32,7 @@ def data t.deposit, t.deposit_credit, t.paid_on, - t.note, + t.note ] end end diff --git a/lib/order_csv.rb b/lib/order_csv.rb index 6ec96581..173b74fb 100644 --- a/lib/order_csv.rb +++ b/lib/order_csv.rb @@ -1,6 +1,9 @@ require 'csv' class OrderCsv < RenderCSV + include ApplicationHelper + include ArticlesHelper + def header [ OrderArticle.human_attribute_name(:units_to_order), @@ -8,20 +11,22 @@ def header Article.human_attribute_name(:name), Article.human_attribute_name(:unit), Article.human_attribute_name(:unit_quantity_short), - ArticlePrice.human_attribute_name(:price), + ArticleVersion.human_attribute_name(:price), OrderArticle.human_attribute_name(:total_price) ] end def data - @object.order_articles.ordered.includes([:article, :article_price]).all.map do |oa| + @object.order_articles.ordered.includes(:article_version).all.map do |oa| yield [ oa.units_to_order, - oa.article.order_number, - oa.article.name, - oa.article.unit, - oa.price.unit_quantity > 1 ? oa.price.unit_quantity : nil, - number_to_currency(oa.price.price * oa.price.unit_quantity), + oa.article_version.order_number, + oa.article_version.name, + format_supplier_order_unit_with_ratios(oa.article_version), + # TODO-article-units: Why should we show the supplier the group order unit quantity?: + oa.article_version.convert_quantity(1, oa.article_version.supplier_order_unit, + oa.article_version.group_order_unit), + number_to_currency(oa.article_version.price), number_to_currency(oa.total_price) ] end diff --git a/lib/order_pdf.rb b/lib/order_pdf.rb index 034ca51f..a5ca69c1 100644 --- a/lib/order_pdf.rb +++ b/lib/order_pdf.rb @@ -1,4 +1,5 @@ class OrderPdf < RenderPDF + include ArticlesHelper attr_reader :order def initialize(order, options = {}) @@ -47,21 +48,44 @@ def nice_table(name, data, dimrows = []) # @return [Number] Price to show # @see https://github.com/foodcoops/foodsoft/issues/445 def order_article_price(order_article) - order_article.price.fc_price + order_article.article_version.fc_group_order_price end def order_article_price_per_unit(order_article) - "#{number_to_currency(order_article_price(order_article))} / #{order_article.article.unit}" + "#{number_to_currency(order_article_price(order_article))} / #{format_group_order_unit_with_ratios(order_article.article_version)}" end - def group_order_article_quantity_with_tolerance(goa) - goa.tolerance > 0 ? "#{goa.quantity} + #{goa.tolerance}" : "#{goa.quantity}" + def price_per_billing_unit(goa) + article_version = goa.order_article.article_version + "#{number_to_currency(article_version.convert_quantity(article_version.fc_price, article_version.billing_unit, + article_version.supplier_order_unit))} / #{format_billing_unit_with_ratios(article_version)}" + end + + def billign_quantity_with_tolerance(goa) + article_version = goa.order_article.article_version + quantity = number_with_precision( + article_version.convert_quantity(goa.quantity, article_version.group_order_unit, + article_version.billing_unit), strip_insignificant_zeros: true, precision: 2 + ) + tolerance = number_with_precision( + article_version.convert_quantity(goa.tolerance, article_version.group_order_unit, + article_version.billing_unit), strip_insignificant_zeros: true, precision: 2 + ) + goa.tolerance > 0 ? "#{quantity} + #{tolerance}" : quantity end def group_order_article_result(goa) number_with_precision goa.result, strip_insignificant_zeros: true end + def billing_article_result(goa) + article_version = goa.order_article.article_version + number_with_precision( + article_version.convert_quantity(goa.result, article_version.group_order_unit, + article_version.billing_unit), precision: 2, strip_insignificant_zeros: true + ) + end + def group_order_articles(ordergroup) GroupOrderArticle .includes(:group_order) @@ -71,11 +95,11 @@ def group_order_articles(ordergroup) def order_articles OrderArticle .ordered - .includes(article: :supplier) + .includes(article_version: { article: :supplier }) .includes(group_order_articles: { group_order: :ordergroup }) .where(order: @orders) - .order('suppliers.name, articles.name, groups.name') - .preload(:article_price) + .order('suppliers.name, article_versions.name, groups.name') + .preload(:article_version) end def ordergroups(offset = nil, limit = nil) @@ -134,9 +158,9 @@ def each_group_order_article_for_order_article(order_article, &block) def each_group_order_article_for_ordergroup(ordergroup, &block) group_order_articles(ordergroup) - .includes(order_article: { article: [:supplier] }) - .order('suppliers.name, articles.name') - .preload(order_article: [:article_price, :order]) + .includes(order_article: { article_version: { article: :supplier } }) + .order('suppliers.name, article_versions.name') + .preload(order_article: %i[article_version order]) .each(&block) end diff --git a/lib/order_txt.rb b/lib/order_txt.rb index acde6ac8..adb25698 100644 --- a/lib/order_txt.rb +++ b/lib/order_txt.rb @@ -1,5 +1,5 @@ class OrderTxt - def initialize(order, options = {}) + def initialize(order, _options = {}) @order = order end @@ -8,23 +8,24 @@ def initialize(order, options = {}) def to_txt supplier = @order.supplier contact = FoodsoftConfig[:contact].symbolize_keys - text = I18n.t('orders.fax.heading', :name => FoodsoftConfig[:name]) - text += "\n#{Supplier.human_attribute_name(:customer_number)}: #{supplier.customer_number}" unless supplier.customer_number.blank? + text = I18n.t('orders.fax.heading', name: FoodsoftConfig[:name]) + text += "\n#{Supplier.human_attribute_name(:customer_number)}: #{supplier.customer_number}" if supplier.customer_number.present? text += "\n" + I18n.t('orders.fax.delivery_day') text += "\n\n#{supplier.name}\n#{supplier.address}\n#{Supplier.human_attribute_name(:fax)}: #{supplier.fax}\n\n" - text += "****** " + I18n.t('orders.fax.to_address') + "\n\n" + text += '****** ' + I18n.t('orders.fax.to_address') + "\n\n" text += "#{FoodsoftConfig[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n" - text += "****** " + I18n.t('orders.fax.articles') + "\n\n" - text += "%8s %8s %s\n" % [I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'), I18n.t('orders.fax.name')] + text += '****** ' + I18n.t('orders.fax.articles') + "\n\n" + text += format("%8s %8s %s\n", I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'), + I18n.t('orders.fax.name')) # now display all ordered articles - @order.order_articles.ordered.includes([:article, :article_price]).each do |oa| - text += "%8s %8d %s\n" % [oa.article.order_number, oa.units_to_order.to_i, oa.article.name] + @order.order_articles.ordered.includes(:article_version).each do |oa| + text += format("%8s %8d %s\n", oa.article_version.order_number, oa.units_to_order.to_i, oa.article_version.name) end text end # Helper method to test pdf via rails console: OrderTxt.new(order).save_tmp def save_tmp - File.open("#{Rails.root}/tmp/#{self.class.to_s.underscore}.txt", 'w') { |f| f.write(to_csv.force_encoding("UTF-8")) } + File.write("#{Rails.root.join("tmp/#{self.class.to_s.underscore}.txt")}", to_csv.force_encoding('UTF-8')) end end diff --git a/lib/ordergroups_csv.rb b/lib/ordergroups_csv.rb index c41d2e83..534bfc06 100644 --- a/lib/ordergroups_csv.rb +++ b/lib/ordergroups_csv.rb @@ -14,7 +14,7 @@ def header Ordergroup.human_attribute_name(:break_start), Ordergroup.human_attribute_name(:break_end), Ordergroup.human_attribute_name(:last_user_activity), - Ordergroup.human_attribute_name(:last_order), + Ordergroup.human_attribute_name(:last_order) ] row + Ordergroup.custom_fields.map { |f| f[:label] } end @@ -33,7 +33,7 @@ def data o.break_start, o.break_end, o.last_user_activity, - o.last_order.try(:starts), + o.last_order.try(:starts) ] yield row + Ordergroup.custom_fields.map { |f| o.settings.custom_fields[f[:name]] } end diff --git a/lib/render_csv.rb b/lib/render_csv.rb index aad84251..5c1203b9 100644 --- a/lib/render_csv.rb +++ b/lib/render_csv.rb @@ -13,7 +13,7 @@ def initialize(object, options = {}) end def to_csv - options = @options.select { |k| %w(col_sep row_sep).include? k.to_s } + options = @options.select { |k| %w[col_sep row_sep].include? k.to_s } ret = CSV.generate options do |csv| if h = header csv << h @@ -34,7 +34,7 @@ def data # Helper method to test pdf via rails console: OrderCsv.new(order).save_tmp def save_tmp encoding = @options[:encoding] || 'UTF-8' - File.open("#{Rails.root}/tmp/#{self.class.to_s.underscore}.csv", 'w') { |f| f.write(to_csv.force_encoding(encoding)) } + File.write("#{Rails.root.join("tmp/#{self.class.to_s.underscore}.csv")}", to_csv.force_encoding(encoding)) end # XXX disable unit to avoid encoding problems, both in unit and whitespace. Also allows computations in spreadsheet. diff --git a/lib/render_pdf.rb b/lib/render_pdf.rb index b79e2a55..4ef65064 100644 --- a/lib/render_pdf.rb +++ b/lib/render_pdf.rb @@ -18,7 +18,7 @@ def skew (height + (border_top_width / 2.0) + (border_bottom_width / 2.0)) / tan_rotation end - def styled_width_of(text) + def styled_width_of(_text) options = @text_options.reject { |k| k == :style } with_font { (@pdf.height_of(@content, options) + padding_top + padding_bottom) / tan_rotation } end @@ -120,7 +120,7 @@ def to_pdf # Helper method to test pdf via rails console: OrderByGroups.new(order).save_tmp def save_tmp - File.open("#{Rails.root}/tmp/#{self.class.to_s.underscore}.pdf", 'w') { |f| f.write(to_pdf.force_encoding("UTF-8")) } + File.write("#{Rails.root.join("tmp/#{self.class.to_s.underscore}.pdf")}", to_pdf.force_encoding('UTF-8')) end # @todo avoid underscore instead of unicode whitespace in pdf :/ @@ -166,6 +166,6 @@ def pdf_add_page_breaks?(docid = nil) end def font_path(name) - File.join(Rails.root, 'vendor', 'assets', 'fonts', name) + Rails.root.join('vendor', 'assets', 'fonts', name).to_s end end diff --git a/lib/tasks/db_shared.rake b/lib/tasks/db_shared.rake new file mode 100644 index 00000000..3f4a7e49 --- /dev/null +++ b/lib/tasks/db_shared.rake @@ -0,0 +1,54 @@ +task spec: ['shared:db:test:prepare'] + +namespace :shared do + namespace :db do |ns| + %i[drop create setup migrate rollback seed version].each do |task_name| + task task_name do + Rake::Task["db:#{task_name}"].invoke + end + end + + namespace :schema do + %i[load dump].each do |task_name| + task task_name do + Rake::Task["db:schema:#{task_name}"].invoke + end + end + end + + namespace :test do + task :prepare do + Rake::Task['db:test:prepare'].invoke + end + end + + # append and prepend proper tasks to all the tasks defined here above + ns.tasks.each do |task| + task.enhance ['shared:set_custom_config'] do + Rake::Task['shared:revert_to_original_config'].invoke + end + end + end + + task set_custom_config: :environment do + # save current vars + @original_config = { + env_schema: ENV.fetch('SCHEMA', nil), + config: Rails.application.config.dup + } + + # set config variables for custom database + ENV['SCHEMA'] = 'db_shared/schema.rb' + Rails.application.config.paths['db'] = ['db_shared'] + Rails.application.config.paths['db/migrate'] = ['db_shared/migrate'] + # If you are using Rails 5 or higher change `paths['db/seeds']` to `paths['db/seeds.rb']` + Rails.application.config.paths['db/seeds'] = ['db_shared/seeds.rb'] + Rails.application.config.paths['config/database'] = ['config/database_shared.yml'] + end + + task revert_to_original_config: :environment do + # reset config variables to original values + ENV['SCHEMA'] = @original_config[:env_schema] + Rails.application.config = @original_config[:config] + end +end diff --git a/lib/tasks/foodsoft.rake b/lib/tasks/foodsoft.rake index 5a5c6fa0..218bb39f 100644 --- a/lib/tasks/foodsoft.rake +++ b/lib/tasks/foodsoft.rake @@ -1,71 +1,71 @@ # put in here all foodsoft tasks # => :environment loads the environment an gives easy access to the application namespace :foodsoft do - desc "Finish ended orders" - task :finish_ended_orders => :environment do + desc 'Finish ended orders' + task finish_ended_orders: :environment do Order.finish_ended! end - desc "Notify users of upcoming tasks" - task :notify_upcoming_tasks => :environment do + desc 'Notify users of upcoming tasks' + task notify_upcoming_tasks: :environment do tasks = Task.where(done: false, due_date: 1.day.from_now.to_date) for task in tasks rake_say "Send notifications for #{task.name} to .." for user in task.users - if user.settings.notify['upcoming_tasks'] - Mailer.deliver_now_with_user_locale user do - Mailer.upcoming_tasks(user, task) - end + next unless user.settings.notify['upcoming_tasks'] + + Mailer.deliver_now_with_user_locale user do + Mailer.upcoming_tasks(user, task) end end end end - desc "Notify workgroup of upcoming weekly task" - task :notify_users_of_weekly_task => :environment do - tasks = Task.where(done: false, due_date: 7.day.from_now.to_date) + desc 'Notify workgroup of upcoming weekly task' + task notify_users_of_weekly_task: :environment do + tasks = Task.where(done: false, due_date: 7.days.from_now.to_date) for task in tasks - unless task.enough_users_assigned? - workgroup = task.workgroup - if workgroup - rake_say "Notify workgroup: #{workgroup.name} for task #{task.name}" - for user in workgroup.users - if user.receive_email? - Mailer.deliver_now_with_user_locale user do - Mailer.not_enough_users_assigned(task, user) - end - end - end + next if task.enough_users_assigned? + + workgroup = task.workgroup + next unless workgroup + + rake_say "Notify workgroup: #{workgroup.name} for task #{task.name}" + for user in workgroup.users + next unless user.receive_email? + + Mailer.deliver_now_with_user_locale user do + Mailer.not_enough_users_assigned(task, user) end end end end - desc "Create upcoming periodic tasks" - task :create_upcoming_periodic_tasks => :environment do + desc 'Create upcoming periodic tasks' + task create_upcoming_periodic_tasks: :environment do for tg in PeriodicTaskGroup.all created_until = tg.create_tasks_for_upfront_days rake_say "created until #{created_until}" end end - desc "Parse incoming email on stdin (options: RECIPIENT=foodcoop.handling)" - task :parse_reply_email => :environment do - FoodsoftMailReceiver.received ENV['RECIPIENT'], STDIN.read + desc 'Parse incoming email on stdin (options: RECIPIENT=foodcoop.handling)' + task parse_reply_email: :environment do + FoodsoftMailReceiver.received ENV.fetch('RECIPIENT', nil), STDIN.read end - desc "Start STMP server for incoming email (options: SMTP_SERVER_PORT=2525, SMTP_SERVER_HOST=0.0.0.0)" - task :reply_email_smtp_server => :environment do + desc 'Start STMP server for incoming email (options: SMTP_SERVER_PORT=2525, SMTP_SERVER_HOST=0.0.0.0)' + task reply_email_smtp_server: :environment do port = ENV['SMTP_SERVER_PORT'].present? ? ENV['SMTP_SERVER_PORT'].to_i : 2525 - host = ENV['SMTP_SERVER_HOST'] + host = ENV.fetch('SMTP_SERVER_HOST', nil) rake_say "Started SMTP server for incoming email on port #{port}." server = FoodsoftMailReceiver.new(ports: port, hosts: host, max_processings: 1, logger: Rails.logger) server.start server.join end - desc "Import and assign bank transactions" - task :import_and_assign_bank_transactions => :environment do + desc 'Import and assign bank transactions' + task import_and_assign_bank_transactions: :environment do BankAccount.find_each do |ba| importer = ba.find_connector next unless importer diff --git a/lib/tasks/foodsoft_setup.rake b/lib/tasks/foodsoft_setup.rake index baa483d1..70b96f26 100644 --- a/lib/tasks/foodsoft_setup.rake +++ b/lib/tasks/foodsoft_setup.rake @@ -9,14 +9,14 @@ module Colors end { - :black => 30, - :red => 31, - :green => 32, - :yellow => 33, - :blue => 34, - :magenta => 35, - :cyan => 36, - :white => 37 + black: 30, + red: 31, + green: 32, + yellow: 33, + blue: 34, + magenta: 35, + cyan: 36, + white: 37 }.each do |key, color_code| define_method key do |text| colorize(text, color_code) @@ -26,31 +26,31 @@ end include Colors namespace :foodsoft do - desc "Setup foodsoft" + desc 'Setup foodsoft' task :setup_development do - puts yellow "This task will help you get your foodcoop running in development." + puts yellow 'This task will help you get your foodcoop running in development.' setup_bundler setup_app_config setup_development setup_database setup_storage start_mailcatcher - puts yellow "All done! Your foodsoft setup should be running smoothly." + puts yellow 'All done! Your foodsoft setup should be running smoothly.' start_server end - desc "Setup foodsoft" + desc 'Setup foodsoft' task :setup_development_docker do - puts yellow "This task will help you get your foodcoop running in development via docker." + puts yellow 'This task will help you get your foodcoop running in development via docker.' setup_app_config setup_development setup_storage setup_run_rake_db_setup - puts yellow "All done! Your foodsoft setup should be running smoothly via docker." + puts yellow 'All done! Your foodsoft setup should be running smoothly via docker.' end namespace :setup do - desc "Initialize stock configuration" + desc 'Initialize stock configuration' task :stock_config do setup_app_config setup_development @@ -59,39 +59,39 @@ namespace :foodsoft do end def setup_bundler - puts yellow "Installing bundler if not installed..." + puts yellow 'Installing bundler if not installed...' %x(if [ -z `which bundle` ]; then gem install bundler --no-rdoc --no-ri; fi) - puts yellow "Executing bundle install..." - %x(bundle install) + puts yellow 'Executing bundle install...' + `bundle install` end def setup_database file = 'config/database.yml' if ENV['DATABASE_URL'] - puts blue "DATABASE_URL found, please remember to also set it when running Foodsoft" + puts blue 'DATABASE_URL found, please remember to also set it when running Foodsoft' return nil end return nil if skip?(file) - database = ask("What kind of database do you use?\nOptions:\n(1) MySQL\n(2) SQLite", ["1", "2"]) - if database == "1" - puts yellow "Using MySQL..." - %x(cp -p #{Rails.root.join("#{file}.MySQL_SAMPLE")} #{Rails.root.join(file)}) - elsif database == "2" - puts yellow "Using SQLite..." - %x(cp -p #{Rails.root.join("#{file}.SQLite_SAMPLE")} #{Rails.root.join(file)}) + database = ask("What kind of database do you use?\nOptions:\n(1) MySQL\n(2) SQLite", %w[1 2]) + if database == '1' + puts yellow 'Using MySQL...' + `cp -p #{Rails.root.join("#{file}.MySQL_SAMPLE")} #{Rails.root.join(file)}` + elsif database == '2' + puts yellow 'Using SQLite...' + `cp -p #{Rails.root.join("#{file}.SQLite_SAMPLE")} #{Rails.root.join(file)}` end reminder(file) - puts blue "IMPORTANT: Edit (rake-generated) config/database.yml with valid username and password for EACH env before continuing!" - finished = ask("Finished?\nOptions:\n(y) Yes", ["y"]) + puts blue 'IMPORTANT: Edit (rake-generated) config/database.yml with valid username and password for EACH env before continuing!' + finished = ask("Finished?\nOptions:\n(y) Yes", ['y']) setup_run_rake_db_setup if finished end def setup_run_rake_db_setup - Rake::Task["db:setup"].reenable - db_setup = capture_stdout { Rake::Task["db:setup"].invoke } + Rake::Task['db:setup'].reenable + db_setup = capture_stdout { Rake::Task['db:setup'].invoke } puts db_setup end @@ -101,7 +101,7 @@ def setup_app_config return nil if skip?(file) puts yellow "Copying #{file}..." - %x(cp -p #{sample} #{Rails.root.join(file)}) + `cp -p #{sample} #{Rails.root.join(file)}` reminder(file) end @@ -110,7 +110,7 @@ def setup_development return nil if skip?(file) puts yellow "Copying #{file}..." - %x(cp -p #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)}) + `cp -p #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)}` reminder(file) end @@ -119,18 +119,18 @@ def setup_storage return nil if skip?(file) puts yellow "Copying #{file}..." - %x(cp -p #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)}) + `cp -p #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)}` reminder(file) end def start_mailcatcher return nil if ENV['MAILCATCHER_PORT'] # skip when it has an existing Docker container - mailcatcher = ask("Do you want to start mailcatcher?\nOptions:\n(y) Yes\n(n) No", ["y", "n"]) - if mailcatcher === "y" - puts yellow "Starting mailcatcher at http://localhost:1080..." - %x(mailcatcher) - end + mailcatcher = ask("Do you want to start mailcatcher?\nOptions:\n(y) Yes\n(n) No", %w[y n]) + return unless mailcatcher === 'y' + + puts yellow 'Starting mailcatcher at http://localhost:1080...' + `mailcatcher` end def start_server @@ -143,7 +143,7 @@ def ask(question, answers = false) puts question input = STDIN.gets.chomp if input.blank? || (answers && !answers.include?(input)) - puts red "Your Input is not valid. Try again!" + puts red 'Your Input is not valid. Try again!' input = ask(question, answers) end input @@ -151,8 +151,11 @@ end def skip?(file) output = false - skip = ask(cyan("We found #{file}!\nOptions:\n(1) Skip step\n(2) Force rewrite"), ["1", "2"]) if File.exists?(Rails.root.join(file)) - output = true if skip == "1" + if File.exist?(Rails.root.join(file)) + skip = ask(cyan("We found #{file}!\nOptions:\n(1) Skip step\n(2) Force rewrite"), + %w[1 2]) + end + output = true if skip == '1' output end diff --git a/lib/tasks/multicoops.rake b/lib/tasks/multicoops.rake index cb2f280a..1e5ef44d 100644 --- a/lib/tasks/multicoops.rake +++ b/lib/tasks/multicoops.rake @@ -4,26 +4,24 @@ namespace :multicoops do desc 'Runs a specific rake task for each registered foodcoop, use rake multicoops:run TASK=db:migrate' - task :run => :environment do - task_to_run = ENV['TASK'] + task run: :environment do + task_to_run = ENV.fetch('TASK', nil) last_error = nil FoodsoftConfig.each_coop do |coop| - begin - rake_say "Run '#{task_to_run}' for #{coop}" - Rake::Task[task_to_run].execute - rescue => error - last_error = error - ExceptionNotifier.notify_exception(error, data: { foodcoop: coop }) - end + rake_say "Run '#{task_to_run}' for #{coop}" + Rake::Task[task_to_run].execute + rescue StandardError => e + last_error = e + ExceptionNotifier.notify_exception(e, data: { foodcoop: coop }) end raise last_error if last_error end desc 'Runs a specific rake task for a single coop, use rake mutlicoops:run_single TASK=db:migrate FOODCOOP=demo' - task :run_single => :environment do - task_to_run = ENV['TASK'] - FoodsoftConfig.select_foodcoop ENV['FOODCOOP'] - rake_say "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" + task run_single: :environment do + task_to_run = ENV.fetch('TASK', nil) + FoodsoftConfig.select_foodcoop ENV.fetch('FOODCOOP', nil) + rake_say "Run '#{task_to_run}' for #{ENV.fetch('FOODCOOP', nil)}" Rake::Task[task_to_run].execute end end diff --git a/lib/tasks/resque.rake b/lib/tasks/resque.rake index 2c50bf69..8705035a 100644 --- a/lib/tasks/resque.rake +++ b/lib/tasks/resque.rake @@ -1,32 +1,32 @@ -require "resque/tasks" +require 'resque/tasks' def run_worker(queue, count = 1) puts "Starting #{count} worker(s) with QUEUE: #{queue}" - ops = { :pgroup => true, :err => ["log/resque_worker_foodsoft_notifier.log", "a"], - :out => ["log/resque_worker_foodsoft_notifier.log", "a"] } - env_vars = { "QUEUE" => queue.to_s, "PIDFILE" => "tmp/pids/resque_worker_foodsoft_notifier.pid" } - count.times { + ops = { pgroup: true, err: ['log/resque_worker_foodsoft_notifier.log', 'a'], + out: ['log/resque_worker_foodsoft_notifier.log', 'a'] } + env_vars = { 'QUEUE' => queue.to_s, 'PIDFILE' => 'tmp/pids/resque_worker_foodsoft_notifier.pid' } + count.times do ## Using Kernel.spawn and Process.detach because regular system() call would ## cause the processes to quit when capistrano finishes - pid = spawn(env_vars, "bundle exec rake resque:work", ops) + pid = spawn(env_vars, 'bundle exec rake resque:work', ops) Process.detach(pid) - } + end end namespace :resque do - task :setup => :environment + task setup: :environment - desc "Restart running workers" + desc 'Restart running workers' task :restart_workers do Rake::Task['resque:stop_workers'].invoke Rake::Task['resque:start_workers'].invoke end - desc "Quit running workers" + desc 'Quit running workers' task :stop_workers do pids = File.read('tmp/pids/resque_worker_foodsoft_notifier.pid').split("\n") if pids.empty? - puts "No workers to kill" + puts 'No workers to kill' else syscmd = "kill -s QUIT #{pids.join(' ')}" puts "Running syscmd: #{syscmd}" @@ -34,8 +34,8 @@ namespace :resque do end end - desc "Start workers" + desc 'Start workers' task :start_workers do - run_worker("foodsoft_notifier") + run_worker('foodsoft_notifier') end end diff --git a/lib/tasks/rspec.rake b/lib/tasks/rspec.rake index 4f0c4081..a90fa407 100644 --- a/lib/tasks/rspec.rake +++ b/lib/tasks/rspec.rake @@ -2,7 +2,7 @@ begin require 'rspec/core/rake_task' task(:spec).clear RSpec::Core::RakeTask.new(:spec) - task :default => :spec + task default: :spec # Use `rspec` to run a single test. When a test fails in rake but not # with rspec, you can use the following to run a single test using rake: diff --git a/lib/tasks/temp_export_supplier.rake b/lib/tasks/temp_export_supplier.rake new file mode 100644 index 00000000..f0826768 --- /dev/null +++ b/lib/tasks/temp_export_supplier.rake @@ -0,0 +1,33 @@ +namespace :temp_supplier do + task export: :environment do + supplier_id = ENV['SUPPLIER_ID']&.to_i + raise 'Missing supplier id' if supplier_id.nil? + + s = Supplier.find_by_id(supplier_id) + article_hashes = s.articles.map do |a| + article_attributes = a.attributes.delete_if { |key| %w[quantity type deleted_at shared_updated_on].include?(key) } + latest_version_attributes = a.latest_article_version.attributes.delete_if do |key| + %w[article_id id].include?(key) + end + aur = a.article_unit_ratios.map do |ratio| + attributes = ratio.attributes + attributes.delete_if { |key| %w[id sort].include?(key) } + attributes + end + + cat = ArticleCategory.find_by_id(latest_version_attributes['article_category_id']) + latest_version_attributes['article_category'] = cat.attributes.delete('id') + latest_version_attributes.delete('article_category_id') + + article_attributes.merge(latest_version_attributes).merge(article_unit_ratios: aur) + end + + puts JSON.pretty_generate({ + api_version: 1, + data: { + changed_after: Time.now, + articles: article_hashes + } + }) + end +end diff --git a/lib/tasks/units.rake b/lib/tasks/units.rake new file mode 100644 index 00000000..3dde517c --- /dev/null +++ b/lib/tasks/units.rake @@ -0,0 +1,136 @@ +namespace :units do + task clean_unece20_source: :environment do + base_unit_entries = [] + + un_ece_20_units = YAML.safe_load(ERB.new(File.read(File.expand_path('config/units-of-measure/un-ece-20.yml', + Rails.root))).result) + re = /^(?:([, 0-9]*)(?: x )!?)?([, 0-9⁰-⁹⁻¹²³]*)?(.*)?$/ + un_ece_20_units.each do |unit| + fixed_conversion_factor = fix_conversion_factor(unit['ConversionFactor']) + md = fixed_conversion_factor.match(re) + multiplier = md[1] + potency = md[2] + base_unit = md[3] + + multiplier&.strip! + potency&.strip! + base_unit&.strip! + + if multiplier.blank? && (!potency.nil? && !/[⁰-⁹⁻¹²³]+/.match(potency)) + multiplier = potency + potency = '' + end + + multiplier&.gsub!(',', '.') + multiplier&.gsub!(' ', '') + multiplier = multiplier.blank? ? 1 : BigDecimal(multiplier) + + unit['conversion'] = {} + unit['conversion']['unit'] = base_unit + + unit['Symbol'] = '' if unit['Symbol'] == unit['CommonCode'] + + power_of_ten = potency.match(/^10 *?([⁰-⁹⁻¹²³]+)$/)&.[](1) + power_of_ten = superscript_to_number(power_of_ten) unless power_of_ten.nil? + + base_unit_multiplier = multiplier + base_unit_multiplier *= (10**power_of_ten) unless power_of_ten.nil? + unit['conversion']['factor'] = base_unit_multiplier.to_s.to_f + is_base_unit = base_unit.present? && potency.blank? && multiplier == 1 + + base_unit_entries << unit if is_base_unit + end + + # rubocop:disable Style/CombinableLoops + un_ece_20_units.each do |unit| + next if unit['conversion']['unit'].blank? + + base_units = base_unit_entries.select { |entry| entry['conversion']['unit'] == unit['conversion']['unit'] } + unit['conversion']['base_units'] = base_units.pluck('CommonCode') + end + + un_ece_20_units.each do |unit| + unit['conversion'].delete('unit') + end + # rubocop:enable Style/CombinableLoops + + result = "# This file has been autogenerated by `rake units:clean_unece20_source`\n#{un_ece_20_units.to_yaml}" + File.write(File.expand_path('config/units-of-measure/un-ece-20-remastered.yml', Rails.root), result) + end + + task build_i18n_from_csv: :environment do + translations = { de: {}, en: {} } + csv_options = { csv_options: { col_sep: ',' } } + SpreadsheetFile.parse 'config/units-of-measure/translations_piece.csv', csv_options do |row, index| + next if index == 0 + + code = row[0] + next if code.nil? # empty csv row + + translation_en = { name: row[2].strip } + translation_en[:abbreviation] = row[3].strip if row[3].present? + translation_en[:aliases] = row[4].split(', ') if row[4].present? + translations[:en][code] = translation_en + + translation_de = { name: row[5].strip } + translation_de[:abbreviation] = row[6].strip if row[6].present? + translation_de[:aliases] = row[7].split(', ') if row[7].present? + translations[:de][code] = translation_de + end + + %i[en de].each do |locale| + SpreadsheetFile.parse "config/units-of-measure/translations_scalar-#{locale}.csv", csv_options do |row, index| + next if index == 0 + + code = row[0] + next if code.nil? # empty csv row + + translation = { name: row[2].strip, long_name: row[1].strip } + translation[:symbols] = [] + translation[:symbols] << row[3].strip if row[3].present? + alternate_symbols = row[4]&.strip + if alternate_symbols.present? + alternate_symbols.split(', ').each do |symbol| + translation[:symbols] << symbol.strip + end + end + translation[:aliases] = row[5].split(', ') if row[5].present? + translations[locale][code] = translation + end + result = "# This file has been autogenerated by `rake units:build_i18n_from_csv`\n#{{ unece_units: translations[locale] }.deep_stringify_keys.to_yaml}" + File.write(File.expand_path("config/units-of-measure/locales/unece_#{locale}.yml", Rails.root), result) + end + end +end + +def superscript_hash + { + '⁻' => '-', '⁰' => '0', '¹' => '1', '²' => '2', '³' => '3', '⁴' => '4', '⁵' => '5', '⁶' => '6', '⁷' => '7', '⁸' => '8', '⁹' => '9' + } +end + +# I know this is stupid, but I couldn't find a default ruby/rails method for this and wouldn't be +# bothered to write a nicer unicode mapper for these few characters: +def superscript_to_number(str) + str = str.chars.map { |c| superscript_hash[c] }.join + + BigDecimal(str) unless str == '' +end + +def number_to_superscript(str) + conversion_hash = superscript_hash.invert + str.chars.map { |c| conversion_hash[c] }.join +end + +def fix_conversion_factor(conversion_factor) + # m2 -> m²: + conversion_factor = conversion_factor.gsub(%r{(\s|^|/)m2(\s|$|/)}, '\1m²\2') + + # m3 -> m³ + conversion_factor = conversion_factor.gsub(%r{(\s|^|/)m3(\s|$|/)}, '\1m³\2') + + # 10-15 m³ -> 10⁻¹⁵ m³: + conversion_factor.gsub(%r{(\s|^|/)10 ?(- ?[0-9]+)(\s|$|/)}) do + Regexp.last_match[1] + '10' + number_to_superscript(Regexp.last_match[2]) + Regexp.last_match[3] + end +end diff --git a/lib/token_verifier.rb b/lib/token_verifier.rb index a8a0f1eb..5f389943 100644 --- a/lib/token_verifier.rb +++ b/lib/token_verifier.rb @@ -19,11 +19,9 @@ def verify(message) raise InvalidPrefix unless r[1] == @_prefix # return original message - if r.length > 2 - r[2] - else - nil - end + return unless r.length > 2 + + r[2] end class InvalidMessage < ActiveSupport::MessageVerifier::InvalidSignature; end @@ -32,8 +30,6 @@ class InvalidScope < ActiveSupport::MessageVerifier::InvalidSignature; end class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end - protected - def self.secret # secret_key_base for Rails 4, but Rails 3 initializer may still be used Foodsoft::Application.config.secret_key_base || Foodsoft::Application.config.secret_token diff --git a/plugins/current_orders/app/controllers/current_orders/articles_controller.rb b/plugins/current_orders/app/controllers/current_orders/articles_controller.rb index 0e4b7dd9..3f48d995 100644 --- a/plugins/current_orders/app/controllers/current_orders/articles_controller.rb +++ b/plugins/current_orders/app/controllers/current_orders/articles_controller.rb @@ -1,6 +1,6 @@ class CurrentOrders::ArticlesController < ApplicationController before_action :authenticate_orders - before_action :find_order_and_order_article, only: [:index, :show] + before_action :find_order_and_order_article, only: %i[index show] def index # sometimes need to pass id as parameter for forms @@ -26,14 +26,14 @@ def show_on_group_order_article_update def find_order_and_order_article @current_orders = Order.finished_not_closed - unless params[:order_id].blank? + if params[:order_id].blank? + @order_articles = OrderArticle.where(order_id: @current_orders.all.map(&:id)) + else @order = Order.find(params[:order_id]) @order_articles = @order.order_articles - else - @order_articles = OrderArticle.where(order_id: @current_orders.all.map(&:id)) end @q = OrderArticle.search(params[:q]) - @order_articles = @order_articles.ordered.merge(@q.result).includes(:article, :article_price) + @order_articles = @order_articles.ordered.merge(@q.result).includes(:article, :article_version) @order_article = @order_articles.where(id: params[:id]).first end diff --git a/plugins/current_orders/app/controllers/current_orders/group_orders_controller.rb b/plugins/current_orders/app/controllers/current_orders/group_orders_controller.rb index 717ebba8..20f6e172 100644 --- a/plugins/current_orders/app/controllers/current_orders/group_orders_controller.rb +++ b/plugins/current_orders/app/controllers/current_orders/group_orders_controller.rb @@ -4,12 +4,12 @@ class CurrentOrders::GroupOrdersController < ApplicationController def index # XXX code duplication lib/foodsoft_current_orders/app/controllers/current_orders/ordergroups_controller.rb - @order_ids = Order.where(state: ['open', 'finished']).all.map(&:id) - @goas = GroupOrderArticle.includes(:group_order => :ordergroup).includes(:order_article) + @order_ids = Order.where(state: %w[open finished]).all.map(&:id) + @goas = GroupOrderArticle.includes(group_order: :ordergroup).includes(:order_article) .where(group_orders: { order_id: @order_ids, ordergroup_id: @ordergroup.id }).ordered - @articles_grouped_by_category = @goas.includes(:order_article => { :article => :article_category }) + @articles_grouped_by_category = @goas.includes(order_article: { article: :article_category }) .order('articles.name') - .group_by { |a| a.order_article.article.article_category.name } + .group_by { |a| a.order_article.article_version.article_category.name } .sort { |a, b| a[0] <=> b[0] } end @@ -18,8 +18,8 @@ def index # XXX code duplication from GroupOrdersController def ensure_ordergroup_member @ordergroup = @current_user.ordergroup - if @ordergroup.nil? - redirect_to root_url, :alert => I18n.t('group_orders.errors.no_member') - end + return unless @ordergroup.nil? + + redirect_to root_url, alert: I18n.t('group_orders.errors.no_member') end end diff --git a/plugins/current_orders/app/controllers/current_orders/ordergroups_controller.rb b/plugins/current_orders/app/controllers/current_orders/ordergroups_controller.rb index 708016a9..62d5d36e 100644 --- a/plugins/current_orders/app/controllers/current_orders/ordergroups_controller.rb +++ b/plugins/current_orders/app/controllers/current_orders/ordergroups_controller.rb @@ -1,6 +1,6 @@ class CurrentOrders::OrdergroupsController < ApplicationController before_action :authenticate_orders - before_action :find_group_orders, only: [:index, :show] + before_action :find_group_orders, only: %i[index show] def index # sometimes need to pass id as parameter for forms @@ -34,12 +34,14 @@ def find_group_orders @all_ordergroups.sort_by! { |o| @ordered_group_ids.include?(o.id) ? o.name : "ZZZZZ#{o.name}" } @ordergroup = Ordergroup.find(params[:id]) unless params[:id].nil? - @goas = GroupOrderArticle.includes(:group_order, :order_article => [:article, :article_price]) - .where(group_orders: { order_id: @order_ids, ordergroup_id: @ordergroup.id }).ordered.all unless @ordergroup.nil? + return if @ordergroup.nil? + + @goas = GroupOrderArticle.includes(:group_order, order_article: %i[article article_version]) + .where(group_orders: { order_id: @order_ids, ordergroup_id: @ordergroup.id }).ordered.all end helper_method \ def articles_for_adding - OrderArticle.includes(:article, :article_price).where(order_id: @order_ids) + OrderArticle.includes(:article, :article_version).where(order_id: @order_ids) end end diff --git a/plugins/current_orders/app/documents/multiple_orders_by_articles.rb b/plugins/current_orders/app/documents/multiple_orders_by_articles.rb index 48d8a058..43966e3b 100644 --- a/plugins/current_orders/app/documents/multiple_orders_by_articles.rb +++ b/plugins/current_orders/app/documents/multiple_orders_by_articles.rb @@ -40,8 +40,8 @@ def body sum[:quantity], nil] # number_to_currency(sum[:price])] - text "#{order_article.article.name} " + - "(#{order_article.article.unit}; #{number_to_currency order_article.price.fc_price}; " + + text "#{order_article.article_version.name} " + + "(#{order_article.article_version.unit}; #{number_to_currency order_article.article_version.fc_price}; " + units_history_line(order_article, plain: true) + ')', size: fontsize(10), inline_format: true table rows, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| @@ -69,19 +69,19 @@ def body protected def pdf_add_page_breaks? - super 'order_by_articles' + super('order_by_articles') end def order_articles OrderArticle.where(order_id: order).ordered .includes(:article).references(:article) .reorder('order_articles.order_id, articles.name') - .preload(:article_price) # preload not join, just in case it went missing - .preload(:order, :group_order_articles => { :group_order => :ordergroup }) + .preload(:article_version) # preload not join, just in case it went missing + .preload(:order, group_order_articles: { group_order: :ordergroup }) end - def each_order_article - order_articles.find_each_with_order(batch_size: BATCH_SIZE) { |oa| yield oa } + def each_order_article(&block) + order_articles.find_each_with_order(batch_size: BATCH_SIZE, &block) end def group_order_articles_for(order_article) @@ -90,7 +90,7 @@ def group_order_articles_for(order_article) goas end - def each_group_order_article_for(group_order) - group_order_articles_for(group_order).each { |goa| yield goa } + def each_group_order_article_for(group_order, &block) + group_order_articles_for(group_order).each(&block) end end diff --git a/plugins/current_orders/app/documents/multiple_orders_by_groups.rb b/plugins/current_orders/app/documents/multiple_orders_by_groups.rb index 0c9eefa9..866f788d 100644 --- a/plugins/current_orders/app/documents/multiple_orders_by_groups.rb +++ b/plugins/current_orders/app/documents/multiple_orders_by_groups.rb @@ -28,18 +28,18 @@ def body has_tolerance = false each_group_order_article_for(ordergroup) do |goa| - has_tolerance = true if goa.order_article.price.unit_quantity > 1 - price = goa.order_article.price.fc_price + has_tolerance = true if goa.order_article.article_version.unit_quantity > 1 + price = goa.order_article.article_version.fc_price sub_total = goa.total_price total += sub_total - rows << [goa.order_article.article.name, + rows << [goa.order_article.article_version.name, goa.group_order.order.name.truncate(10, omission: ''), number_to_currency(price), - goa.order_article.article.unit, + goa.order_article.article_version.unit, goa.tolerance > 0 ? "#{goa.quantity} + #{goa.tolerance}" : goa.quantity, group_order_article_result(goa), number_to_currency(sub_total), - goa.order_article.price.unit_quantity] + goa.order_article.article_version.unit_quantity] dimrows << rows.length if goa.result == 0 end next if rows.length == 0 @@ -56,7 +56,7 @@ def body I18n.t('shared.articles.ordered'), I18n.t('shared.articles.received'), I18n.t('shared.articles_by.price_sum'), - { image: "#{Rails.root}/app/assets/images/package-bg.png", scale: 0.6, position: :center } + { image: "#{Rails.root.join('app/assets/images/package-bg.png')}", scale: 0.6, position: :center } ] # last column showing unit_quantity is useless if they're all one @@ -100,7 +100,7 @@ def body protected def pdf_add_page_breaks? - super 'order_by_groups' + super('order_by_groups') end def ordergroups @@ -113,7 +113,7 @@ def ordergroups s end - def each_ordergroup + def each_ordergroup(&block) ordergroups.find_in_batches_with_order(batch_size: BATCH_SIZE) do |ordergroups| @group_order_article_batch = GroupOrderArticle .joins(:group_order) @@ -121,8 +121,8 @@ def each_ordergroup .where(group_orders: { ordergroup_id: ordergroups.map(&:id) }) .order('group_orders.order_id, group_order_articles.id') .preload(group_orders: { order: :supplier }) - .preload(order_article: [:article, :article_price, :order]) - ordergroups.each { |ordergroup| yield ordergroup } + .preload(order_article: %i[article article_version order]) + ordergroups.each(&block) end end @@ -130,7 +130,7 @@ def group_order_articles_for(ordergroup) @group_order_article_batch.select { |goa| goa.group_order.ordergroup_id == ordergroup.id } end - def each_group_order_article_for(ordergroup) - group_order_articles_for(ordergroup).each { |goa| yield goa } + def each_group_order_article_for(ordergroup, &block) + group_order_articles_for(ordergroup).each(&block) end end diff --git a/plugins/current_orders/app/helpers/current_orders_helper.rb b/plugins/current_orders/app/helpers/current_orders_helper.rb index 3bbab482..40c0fe45 100644 --- a/plugins/current_orders/app/helpers/current_orders_helper.rb +++ b/plugins/current_orders/app/helpers/current_orders_helper.rb @@ -6,7 +6,8 @@ def to_pay_message(ordergroup) elsif funds == 0 I18n.t('helpers.current_orders.pay_none') else - content_tag :b, I18n.t('helpers.current_orders.pay_amount', amount: number_to_currency(-ordergroup.get_available_funds)) + content_tag :b, + I18n.t('helpers.current_orders.pay_amount', amount: number_to_currency(-ordergroup.get_available_funds)) end end end diff --git a/plugins/current_orders/app/views/current_orders/articles/_article_info.html.haml b/plugins/current_orders/app/views/current_orders/articles/_article_info.html.haml index 6b2f0f4c..a901e3a8 100644 --- a/plugins/current_orders/app/views/current_orders/articles/_article_info.html.haml +++ b/plugins/current_orders/app/views/current_orders/articles/_article_info.html.haml @@ -1,22 +1,22 @@ #article_info %h2{style: 'margin-bottom: 0'} - = t('current_orders.articles.show.title', name: order_article.article.name) - %span.normal= order_article.article.unit + = t('current_orders.articles.show.title', name: order_article.article_version.name) + %span.normal= order_article.article_version.unit -# @todo unduplicate from group_orders's order_article_info %p - - if order_article.article.manufacturer.blank? + - if order_article.article_version.manufacturer.blank? = raw t '.supplied_by', supplier: content_tag(:em, supplier_link(order_article.order)) - - elsif order_article.article.supplier && order_article.article.supplier.name == order_article.article.manufacturer + - elsif order_article.article_version.article.supplier && order_article.article_version.article.supplier.name == order_article.article_version.manufacturer = raw t '.supplied_and_made_by', manufacturer: content_tag(:em, supplier_link(order_article.order)) - else - = raw t '.supplied_by_made_by', supplier: content_tag(:em, supplier_link(order_article.order)), manufacturer: content_tag(:em, order_article.article.manufacturer) - - unless order_article.article.origin.blank? - = raw t '.origin_in', origin: content_tag(:em, order_article.article.origin) + = raw t '.supplied_by_made_by', supplier: content_tag(:em, supplier_link(order_article.order)), manufacturer: content_tag(:em, order_article.article_version.manufacturer) + - unless order_article.article_version.origin.blank? + = raw t '.origin_in', origin: content_tag(:em, order_article.article_version.origin) - pkg_info = pkg_helper(order_article.price) = ", #{pkg_info}".html_safe unless pkg_info.blank? = ", " = Article.human_attribute_name(:fc_price_short) + ": " - = number_to_currency(order_article.price.fc_price) - = t '.unit', unit: order_article.article.unit + = number_to_currency(order_article.article_version.fc_price) + = t '.unit', unit: order_article.article_version.unit diff --git a/plugins/current_orders/app/views/current_orders/articles/_articles.html.haml b/plugins/current_orders/app/views/current_orders/articles/_articles.html.haml index ab00d38b..e53476ac 100644 --- a/plugins/current_orders/app/views/current_orders/articles/_articles.html.haml +++ b/plugins/current_orders/app/views/current_orders/articles/_articles.html.haml @@ -1,4 +1,4 @@ - @order_articles.includes(:order => :supplier).reorder('suppliers.name, articles.article_category_id, articles.name').group_by {|oa| oa.order.name}.each do |name, articles| %li.nav-header= name - articles.each do |oa| - %li= link_to oa.article.name, current_orders_articles_path(order_id: oa.order_id, id: oa.id, anchor: 'order_article'), remote: true + %li= link_to oa.article_version. name, current_orders_articles_path(order_id: oa.order_id, id: oa.id, anchor: 'order_article'), remote: true diff --git a/plugins/current_orders/app/views/current_orders/articles/_ordergroups.html.haml b/plugins/current_orders/app/views/current_orders/articles/_ordergroups.html.haml index 908b7571..03194ef2 100644 --- a/plugins/current_orders/app/views/current_orders/articles/_ordergroups.html.haml +++ b/plugins/current_orders/app/views/current_orders/articles/_ordergroups.html.haml @@ -30,5 +30,5 @@ %td %td %td.center - %span.units_delta{data: {'quantity-expected' => order_article.units * order_article.price.unit_quantity}} + %span.units_delta{data: {'quantity-expected' => order_article.units * order_article.article_version.unit_quantity}} %td diff --git a/plugins/current_orders/app/views/current_orders/articles/show.html.haml b/plugins/current_orders/app/views/current_orders/articles/show.html.haml index e6d600f8..0c77180c 100644 --- a/plugins/current_orders/app/views/current_orders/articles/show.html.haml +++ b/plugins/current_orders/app/views/current_orders/articles/show.html.haml @@ -1,3 +1,3 @@ -- title t('.title', name: @order_article.article.name), false +- title t('.title', name: @order_article.article_version.name), false = render 'form' diff --git a/plugins/current_orders/app/views/current_orders/group_orders/_result.html.haml b/plugins/current_orders/app/views/current_orders/group_orders/_result.html.haml index 7d247d7d..b891160d 100644 --- a/plugins/current_orders/app/views/current_orders/group_orders/_result.html.haml +++ b/plugins/current_orders/app/views/current_orders/group_orders/_result.html.haml @@ -30,21 +30,21 @@ - group_order_sum += r[:sub_total] %tr{class: cycle('even', 'odd', name: 'articles') + " order-article " + order_article_class_name(r[:quantity], r[:tolerance], r[:result])} -# article_info is present in foodcoop-adam only - %td.name{style: "width:40%", title: (article_info_title(oa.article) rescue nil)} - = article_info_icon oa.article rescue nil - = oa.article.name + %td.name{style: "width:40%", title: (article_info_title(oa.article_version) rescue nil)} + = article_info_icon oa.article_version rescue nil + = oa.article_version. name %td - = oa.article.unit + = oa.article_version. unit %span{style: 'opacity: 0.4; margin-left: 1em;'}= pkg_helper(oa.price, soft_uq: true) - %td= number_to_currency oa.price.fc_price + %td= number_to_currency oa.article_version.fc_price %td = r[:quantity] - = "+ #{r[:tolerance]}" if oa.price.unit_quantity > 1 + = "+ #{r[:tolerance]}" if oa.article_version.unit_quantity > 1 %td= r[:result] > 0 ? r[:result] : "0" %td= number_to_currency(r[:sub_total]) - - unless oa.article.note.blank? + - unless oa.article_version. note.blank? %tr{id: "note_#{oa.id}", class: "note even", style: "display:none"} - %td{colspan: "6"}=h oa.article.note + %td{colspan: "6"}=h oa.article_version. note %tr{class: cycle('even', 'odd', name: 'articles')} %th{colspan: "5"}= heading_helper GroupOrder, :price %th= number_to_currency(group_order_sum) diff --git a/plugins/current_orders/app/views/current_orders/ordergroups/_article.html.haml b/plugins/current_orders/app/views/current_orders/ordergroups/_article.html.haml index 4659f788..ad58d1a3 100644 --- a/plugins/current_orders/app/views/current_orders/ordergroups/_article.html.haml +++ b/plugins/current_orders/app/views/current_orders/ordergroups/_article.html.haml @@ -1,12 +1,12 @@ -# output row %tr{:class => [cycle('even', 'odd', :name => 'articles'), if goa.result == 0 then 'unavailable' end], id: "goa_#{goa.id}"} - %td.name= goa.order_article.article.name + %td.name= goa.order_article.article_version.name %td{title: goa.order_article.order.name}= link_to goa.order_article.order.name.truncate(15), goa.order_article.order - %td= goa.order_article.article.unit + %td= goa.order_article.article_version.unit %td.center= "#{goa.quantity} + #{goa.tolerance}" %td.center.input-delta= group_order_article_edit_result(goa) %td.symbol × - %td= number_to_currency goa.order_article.price.fc_price + %td= number_to_currency goa.order_article.article_version.fc_price %td.symbol = %td.price{data: {value: goa.total_price}}= number_to_currency goa.total_price diff --git a/plugins/current_orders/app/views/current_orders/ordergroups/_articles.html.haml b/plugins/current_orders/app/views/current_orders/ordergroups/_articles.html.haml index 881baa0c..9db7e475 100644 --- a/plugins/current_orders/app/views/current_orders/ordergroups/_articles.html.haml +++ b/plugins/current_orders/app/views/current_orders/ordergroups/_articles.html.haml @@ -28,7 +28,7 @@ %tfoot %tr %td{colspan: 9} - - new_article_data = articles_for_select2(articles_for_adding) {|a| "#{a.article.name} (#{a.article.unit}, #{number_to_currency a.price.fc_price})"} + - new_article_data = articles_for_select2(articles_for_adding) {|a| "#{a.article.name} (#{a.article.unit}, #{number_to_currency a.article_version.fc_price})"} = form_for GroupOrderArticle.new, remote: true, html: {'data-submit-onchange' => true, style: 'margin: 0'} do |f| = f.select :order_article_id, options_for_select(new_article_data.map {|a| [a[:text], a[:id]]}), diff --git a/plugins/current_orders/config/routes.rb b/plugins/current_orders/config/routes.rb index f642fc31..aeb2b014 100644 --- a/plugins/current_orders/config/routes.rb +++ b/plugins/current_orders/config/routes.rb @@ -1,27 +1,27 @@ Rails.application.routes.draw do scope '/:foodcoop' do namespace :current_orders do - resources :ordergroups, :only => [:index, :show] do + resources :ordergroups, only: %i[index show] do collection do get :show_on_group_order_article_create get :show_on_group_order_article_update end end - resources :articles, :only => [:index, :show] do + resources :articles, only: %i[index show] do collection do get :show_on_group_order_article_create end end - resource :orders, :only => [:show] do + resource :orders, only: [:show] do collection do get :my get :receive end end - resources :group_orders, :only => [:index] + resources :group_orders, only: [:index] end end end diff --git a/plugins/current_orders/foodsoft_current_orders.gemspec b/plugins/current_orders/foodsoft_current_orders.gemspec index c444fbec..f8e22ce6 100644 --- a/plugins/current_orders/foodsoft_current_orders.gemspec +++ b/plugins/current_orders/foodsoft_current_orders.gemspec @@ -1,20 +1,21 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_current_orders/version" +require 'foodsoft_current_orders/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_current_orders" + s.name = 'foodsoft_current_orders' s.version = FoodsoftCurrentOrders::VERSION - s.authors = ["wvengen"] - s.email = ["dev-voko@willem.engen.nl"] - s.homepage = "https://github.com/foodcoop-adam/foodsoft" - s.summary = "Quick support for working on all currently active orders in foodsoft." - s.description = "" + s.authors = ['wvengen'] + s.email = ['dev-voko@willem.engen.nl'] + s.homepage = 'https://github.com/foodcoop-adam/foodsoft' + s.summary = 'Quick support for working on all currently active orders in foodsoft.' + s.description = '' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "deface", "~> 1.0" + s.add_dependency 'rails' + s.add_dependency 'deface', '~> 1.0' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/current_orders/lib/foodsoft_current_orders.rb b/plugins/current_orders/lib/foodsoft_current_orders.rb index 5a9a7c82..8227bea4 100644 --- a/plugins/current_orders/lib/foodsoft_current_orders.rb +++ b/plugins/current_orders/lib/foodsoft_current_orders.rb @@ -1,5 +1,5 @@ -require "deface" -require "foodsoft_current_orders/engine" +require 'deface' +require 'foodsoft_current_orders/engine' module FoodsoftCurrentOrders def self.enabled? diff --git a/plugins/current_orders/lib/foodsoft_current_orders/engine.rb b/plugins/current_orders/lib/foodsoft_current_orders/engine.rb index 07427b56..c6236acb 100644 --- a/plugins/current_orders/lib/foodsoft_current_orders/engine.rb +++ b/plugins/current_orders/lib/foodsoft_current_orders/engine.rb @@ -4,12 +4,15 @@ def navigation(primary, context) return unless FoodsoftCurrentOrders.enabled? return if primary[:orders].nil? - cond = Proc.new { current_user.role_orders? } + cond = proc { current_user.role_orders? } [ SimpleNavigation::Item.new(primary, :stage_divider, nil, nil, class: 'divider', if: cond), - SimpleNavigation::Item.new(primary, :current_orders_receive, I18n.t('current_orders.navigation.receive'), context.receive_current_orders_orders_path, if: cond), - SimpleNavigation::Item.new(primary, :current_orders_articles, I18n.t('current_orders.navigation.articles'), context.current_orders_articles_path, if: cond), - SimpleNavigation::Item.new(primary, :current_orders_ordergroups, I18n.t('current_orders.navigation.ordergroups'), context.current_orders_ordergroups_path, if: cond) + SimpleNavigation::Item.new(primary, :current_orders_receive, I18n.t('current_orders.navigation.receive'), + context.receive_current_orders_orders_path, if: cond), + SimpleNavigation::Item.new(primary, :current_orders_articles, I18n.t('current_orders.navigation.articles'), + context.current_orders_articles_path, if: cond), + SimpleNavigation::Item.new(primary, :current_orders_ordergroups, + I18n.t('current_orders.navigation.ordergroups'), context.current_orders_ordergroups_path, if: cond) ].each { |i| primary[:orders].sub_navigation.items << i } end end diff --git a/plugins/current_orders/lib/foodsoft_current_orders/version.rb b/plugins/current_orders/lib/foodsoft_current_orders/version.rb index af58aa9c..9e916ba6 100644 --- a/plugins/current_orders/lib/foodsoft_current_orders/version.rb +++ b/plugins/current_orders/lib/foodsoft_current_orders/version.rb @@ -1,3 +1,3 @@ module FoodsoftCurrentOrders - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/discourse/Rakefile b/plugins/discourse/Rakefile old mode 100644 new mode 100755 index cb56e2e5..abec3d42 --- a/plugins/discourse/Rakefile +++ b/plugins/discourse/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/discourse/app/controllers/discourse_controller.rb b/plugins/discourse/app/controllers/discourse_controller.rb index 5a65f61c..0e62f14d 100644 --- a/plugins/discourse/app/controllers/discourse_controller.rb +++ b/plugins/discourse/app/controllers/discourse_controller.rb @@ -11,7 +11,7 @@ def valid_signature? def redirect_to_with_payload(url, payload) base64_payload = Base64.strict_encode64 payload.to_query - sso = CGI::escape base64_payload + sso = CGI.escape base64_payload sig = get_hmac_hex_string base64_payload redirect_to "#{url}#{url.include?('?') ? '&' : '?'}sso=#{sso}&sig=#{sig}" end @@ -21,7 +21,7 @@ def parse_payload payload.symbolize_keys! end - def get_hmac_hex_string payload + def get_hmac_hex_string(payload) discourse_sso_secret = FoodsoftConfig[:discourse_sso_secret] OpenSSL::HMAC.hexdigest 'sha256', discourse_sso_secret, payload end diff --git a/plugins/discourse/app/controllers/discourse_login_controller.rb b/plugins/discourse/app/controllers/discourse_login_controller.rb index 1c8fe938..bd7a81e3 100644 --- a/plugins/discourse/app/controllers/discourse_login_controller.rb +++ b/plugins/discourse/app/controllers/discourse_login_controller.rb @@ -5,7 +5,7 @@ class DiscourseLoginController < DiscourseController def initiate discourse_url = FoodsoftConfig[:discourse_url] - nonce = SecureRandom.hex() + nonce = SecureRandom.hex return_sso_url = url_for(action: :callback, only_path: false) session[:discourse_sso_nonce] = nonce @@ -36,7 +36,7 @@ def callback user.save! login_and_redirect_to_return_to user, notice: I18n.t('discourse.callback.logged_in') - rescue => error - redirect_to login_url, alert: error.to_s + rescue StandardError => e + redirect_to login_url, alert: e.to_s end end diff --git a/plugins/discourse/app/controllers/discourse_sso_controller.rb b/plugins/discourse/app/controllers/discourse_sso_controller.rb index e8f742b6..04dc8d1c 100644 --- a/plugins/discourse/app/controllers/discourse_sso_controller.rb +++ b/plugins/discourse/app/controllers/discourse_sso_controller.rb @@ -17,7 +17,7 @@ def sso external_id: "#{FoodsoftConfig.scope}/#{current_user.id}", username: current_user.nick, name: current_user.name - rescue => error - redirect_to root_url, alert: error.to_s + rescue StandardError => e + redirect_to root_url, alert: e.to_s end end diff --git a/plugins/discourse/foodsoft_discourse.gemspec b/plugins/discourse/foodsoft_discourse.gemspec index 6d2fb5d4..1e6113ce 100644 --- a/plugins/discourse/foodsoft_discourse.gemspec +++ b/plugins/discourse/foodsoft_discourse.gemspec @@ -1,21 +1,21 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_discourse/version" +require 'foodsoft_discourse/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_discourse" + s.name = 'foodsoft_discourse' s.version = FoodsoftDiscourse::VERSION - s.authors = ["paroga"] - s.email = ["paroga@paroga.com"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Discourse plugin for foodsoft." - s.description = "Allow SSO login via Discourse" + s.authors = ['paroga'] + s.email = ['paroga@paroga.com'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Discourse plugin for foodsoft.' + s.description = 'Allow SSO login via Discourse' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "deface", "~> 1.0" + s.add_dependency 'rails' + s.add_dependency 'deface', '~> 1.0' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb b/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb index 901979b1..6ddfdb13 100644 --- a/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb +++ b/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb @@ -2,7 +2,7 @@ module FoodsoftDiscourse module RedirectToLogin def self.included(base) # :nodoc: base.class_eval do - alias foodsoft_discourse_orig_redirect_to_login redirect_to_login + alias_method :foodsoft_discourse_orig_redirect_to_login, :redirect_to_login def redirect_to_login(options = {}) if FoodsoftDiscourse.enabled? && !FoodsoftConfig[:discourse_sso] @@ -18,5 +18,5 @@ def redirect_to_login(options = {}) # modify existing helper ActiveSupport.on_load(:after_initialize) do - Concerns::Auth.send :include, FoodsoftDiscourse::RedirectToLogin + Concerns::Auth.include FoodsoftDiscourse::RedirectToLogin end diff --git a/plugins/discourse/lib/foodsoft_discourse/version.rb b/plugins/discourse/lib/foodsoft_discourse/version.rb index 2b8d4138..60d38a51 100644 --- a/plugins/discourse/lib/foodsoft_discourse/version.rb +++ b/plugins/discourse/lib/foodsoft_discourse/version.rb @@ -1,3 +1,3 @@ module FoodsoftDiscourse - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/documents/Rakefile b/plugins/documents/Rakefile old mode 100644 new mode 100755 index 2834c5f3..861a530a --- a/plugins/documents/Rakefile +++ b/plugins/documents/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/documents/app/controllers/documents_controller.rb b/plugins/documents/app/controllers/documents_controller.rb index b97470a5..7d1aea30 100644 --- a/plugins/documents/app/controllers/documents_controller.rb +++ b/plugins/documents/app/controllers/documents_controller.rb @@ -4,16 +4,16 @@ class DocumentsController < ApplicationController before_action -> { require_plugin_enabled FoodsoftDocuments } def index - if params["sort"] - sort = case params["sort"] - when "name" then "data IS NULL DESC, name" - when "created_at" then "created_at" - when "name_reverse" then "data IS NULL, name DESC" - when "created_at_reverse" then "created_at DESC" + sort = if params['sort'] + case params['sort'] + when 'name' then 'data IS NULL DESC, name' + when 'created_at' then 'created_at' + when 'name_reverse' then 'data IS NULL, name DESC' + when 'created_at_reverse' then 'created_at DESC' end - else - sort = "data IS NULL DESC, name" - end + else + 'data IS NULL DESC, name' + end @documents = Document.where(parent: @document).page(params[:page]).per(@per_page).order(sort) end @@ -40,16 +40,16 @@ def create @document.created_by = current_user @document.save! redirect_to @document.parent || documents_path, notice: t('.notice') - rescue => error - redirect_to @document.parent || documents_path, alert: t('.error', error: error.message) + rescue StandardError => e + redirect_to @document.parent || documents_path, alert: t('.error', error: e.message) end def update @document = Document.find(params[:id]) @document.update_attribute(:parent_id, params[:parent_id]) redirect_to @document.parent || documents_path, notice: t('.notice') - rescue => error - redirect_to @document.parent || documents_path, alert: t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to @document.parent || documents_path, alert: t('errors.general_msg', msg: e.message) end def destroy @@ -60,8 +60,8 @@ def destroy else redirect_to documents_path, alert: t('.no_right') end - rescue => error - redirect_to documents_path, alert: t('.error', error: error.message) + rescue StandardError => e + redirect_to documents_path, alert: t('.error', error: e.message) end def show diff --git a/plugins/documents/foodsoft_documents.gemspec b/plugins/documents/foodsoft_documents.gemspec index c0a4f8fd..63d3b428 100644 --- a/plugins/documents/foodsoft_documents.gemspec +++ b/plugins/documents/foodsoft_documents.gemspec @@ -1,22 +1,22 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_documents/version" +require 'foodsoft_documents/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_documents" + s.name = 'foodsoft_documents' s.version = FoodsoftDocuments::VERSION - s.authors = ["paroga"] - s.email = ["paroga@paroga.com"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Documents plugin for foodsoft." - s.description = "Adds simple document management to foodsoft." + s.authors = ['paroga'] + s.email = ['paroga@paroga.com'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Documents plugin for foodsoft.' + s.description = 'Adds simple document management to foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "deface", "~> 1.0" - s.add_dependency "ruby-filemagic" + s.add_dependency 'rails' + s.add_dependency 'deface', '~> 1.0' + s.add_dependency 'ruby-filemagic' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/documents/lib/foodsoft_documents/engine.rb b/plugins/documents/lib/foodsoft_documents/engine.rb index 970b3aa5..de81904c 100644 --- a/plugins/documents/lib/foodsoft_documents/engine.rb +++ b/plugins/documents/lib/foodsoft_documents/engine.rb @@ -8,9 +8,9 @@ def navigation(primary, context) sub_nav.items << SimpleNavigation::Item.new(primary, :documents, I18n.t('navigation.documents'), context.documents_path) # move to right before tasks item - if i = sub_nav.items.index(sub_nav[:tasks]) - sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) - end + return unless i = sub_nav.items.index(sub_nav[:tasks]) + + sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) end def default_foodsoft_config(cfg) diff --git a/plugins/documents/lib/foodsoft_documents/version.rb b/plugins/documents/lib/foodsoft_documents/version.rb index 6e57dbb3..7096e468 100644 --- a/plugins/documents/lib/foodsoft_documents/version.rb +++ b/plugins/documents/lib/foodsoft_documents/version.rb @@ -1,3 +1,3 @@ module FoodsoftDocuments - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/links/Rakefile b/plugins/links/Rakefile old mode 100644 new mode 100755 index fb6356f8..a9902fd3 --- a/plugins/links/Rakefile +++ b/plugins/links/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/links/app/controllers/admin/links_controller.rb b/plugins/links/app/controllers/admin/links_controller.rb index e7b60aa5..580c8a93 100644 --- a/plugins/links/app/controllers/admin/links_controller.rb +++ b/plugins/links/app/controllers/admin/links_controller.rb @@ -37,8 +37,8 @@ def destroy link = Link.find(params[:id]) link.destroy! redirect_to admin_links_path - rescue => error - redirect_to admin_links_path, I18n.t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to admin_links_path, I18n.t('errors.general_msg', msg: e.message) end private diff --git a/plugins/links/app/controllers/links_controller.rb b/plugins/links/app/controllers/links_controller.rb index 143fc63d..27615517 100644 --- a/plugins/links/app/controllers/links_controller.rb +++ b/plugins/links/app/controllers/links_controller.rb @@ -5,9 +5,7 @@ def show link = Link.find(params[:id]) url = link.url - if link.workgroup && !current_user.role_admin? && !link.workgroup.member?(current_user) - return deny_access - end + return deny_access if link.workgroup && !current_user.role_admin? && !link.workgroup.member?(current_user) if link.indirect uri = URI.parse url @@ -19,11 +17,9 @@ def show url = result.header['Location'] - unless url - return redirect_to root_url, alert: t('.indirect_no_location') - end + return redirect_to root_url, alert: t('.indirect_no_location') unless url end - redirect_to url, status: 302 + redirect_to url, status: :found end end diff --git a/plugins/links/foodsoft_links.gemspec b/plugins/links/foodsoft_links.gemspec index 04fc03f9..c879f4b1 100644 --- a/plugins/links/foodsoft_links.gemspec +++ b/plugins/links/foodsoft_links.gemspec @@ -1,21 +1,21 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_links/version" +require 'foodsoft_links/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_links" + s.name = 'foodsoft_links' s.version = FoodsoftLinks::VERSION - s.authors = ["paroga"] - s.email = ["paroga@paroga.com"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Links plugin for foodsoft." - s.description = "Adds simple link management to foodsoft." + s.authors = ['paroga'] + s.email = ['paroga@paroga.com'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Links plugin for foodsoft.' + s.description = 'Adds simple link management to foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "deface", "~> 1.0" + s.add_dependency 'rails' + s.add_dependency 'deface', '~> 1.0' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/links/lib/foodsoft_links/engine.rb b/plugins/links/lib/foodsoft_links/engine.rb index ab6d9175..52672597 100644 --- a/plugins/links/lib/foodsoft_links/engine.rb +++ b/plugins/links/lib/foodsoft_links/engine.rb @@ -1,7 +1,7 @@ module FoodsoftLinks class Engine < ::Rails::Engine def navigation(primary, context) - primary.item :links, I18n.t('navigation.links'), '#', if: Proc.new { visble_links(context).any? } do |subnav| + primary.item :links, I18n.t('navigation.links'), '#', if: proc { visble_links(context).any? } do |subnav| visble_links(context).each do |link| subnav.item link.id, link.name, context.link_path(link) end @@ -11,15 +11,15 @@ def navigation(primary, context) primary.items.insert(i, primary.items.delete_at(-1)) end - unless primary[:admin].nil? - sub_nav = primary[:admin].sub_navigation - sub_nav.items << - SimpleNavigation::Item.new(primary, :links, I18n.t('navigation.admin.links'), context.admin_links_path) - # move to right before config item - if i = sub_nav.items.index(sub_nav[:config]) - sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) - end - end + return if primary[:admin].nil? + + sub_nav = primary[:admin].sub_navigation + sub_nav.items << + SimpleNavigation::Item.new(primary, :links, I18n.t('navigation.admin.links'), context.admin_links_path) + # move to right before config item + return unless i = sub_nav.items.index(sub_nav[:config]) + + sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) end def visble_links(context) diff --git a/plugins/links/lib/foodsoft_links/version.rb b/plugins/links/lib/foodsoft_links/version.rb index 20341ca4..707cfdb3 100644 --- a/plugins/links/lib/foodsoft_links/version.rb +++ b/plugins/links/lib/foodsoft_links/version.rb @@ -1,3 +1,3 @@ module FoodsoftLinks - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/messages/Rakefile b/plugins/messages/Rakefile old mode 100644 new mode 100755 index ac014bdd..9e2cbeab --- a/plugins/messages/Rakefile +++ b/plugins/messages/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/messages/app/controllers/admin/messagegroups_controller.rb b/plugins/messages/app/controllers/admin/messagegroups_controller.rb index cce57474..76fbbfb9 100644 --- a/plugins/messages/app/controllers/admin/messagegroups_controller.rb +++ b/plugins/messages/app/controllers/admin/messagegroups_controller.rb @@ -4,7 +4,7 @@ class Admin::MessagegroupsController < Admin::BaseController def index @messagegroups = Messagegroup.order('name ASC') # if somebody uses the search field: - @messagegroups = @messagegroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].blank? + @messagegroups = @messagegroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present? @messagegroups = @messagegroups.page(params[:page]).per(@per_page) end @@ -13,7 +13,7 @@ def destroy @messagegroup = Messagegroup.find(params[:id]) @messagegroup.destroy redirect_to admin_messagegroups_url, notice: t('admin.messagegroups.destroy.notice') - rescue => error - redirect_to admin_messagegroups_url, alert: t('admin.messagegroups.destroy.error', error: error.message) + rescue StandardError => e + redirect_to admin_messagegroups_url, alert: t('admin.messagegroups.destroy.error', error: e.message) end end diff --git a/plugins/messages/app/controllers/messagegroups_controller.rb b/plugins/messages/app/controllers/messagegroups_controller.rb index e9ba6770..7629eab8 100644 --- a/plugins/messages/app/controllers/messagegroups_controller.rb +++ b/plugins/messages/app/controllers/messagegroups_controller.rb @@ -1,17 +1,17 @@ class MessagegroupsController < ApplicationController def index - @messagegroups = Messagegroup.order("name") + @messagegroups = Messagegroup.order('name') end def join @messagegroup = Messagegroup.find(params[:id]) @messagegroup.users << current_user - redirect_to messagegroups_url, :notice => I18n.t('messagegroups.join.notice') + redirect_to messagegroups_url, notice: I18n.t('messagegroups.join.notice') end def leave @messagegroup = Messagegroup.find(params[:id]) @messagegroup.users.destroy(current_user) - redirect_to messagegroups_url, :notice => I18n.t('messagegroups.leave.notice') + redirect_to messagegroups_url, notice: I18n.t('messagegroups.leave.notice') end end diff --git a/plugins/messages/app/controllers/messages_controller.rb b/plugins/messages/app/controllers/messages_controller.rb index 2c594da3..bd043d58 100644 --- a/plugins/messages/app/controllers/messages_controller.rb +++ b/plugins/messages/app/controllers/messages_controller.rb @@ -10,21 +10,20 @@ def index def new @message = Message.new(params[:message]) - if @message.reply_to - original_message = Message.find(@message.reply_to) - if original_message.reply_to - @message.reply_to = original_message.reply_to - end - if original_message.is_readable_for?(current_user) - @message.add_recipients [original_message.sender_id] - @message.group_id = original_message.group_id - @message.private = original_message.private - @message.subject = I18n.t('messages.model.reply_subject', :subject => original_message.subject) - @message.body = I18n.t('messages.model.reply_header', :user => original_message.sender.display, :when => I18n.l(original_message.created_at, :format => :short)) + "\n" - original_message.body.each_line { |l| @message.body += I18n.t('messages.model.reply_indent', :line => l) } - else - redirect_to new_message_url, alert: I18n.t('messages.new.error_private') - end + return unless @message.reply_to + + original_message = Message.find(@message.reply_to) + @message.reply_to = original_message.reply_to if original_message.reply_to + if original_message.is_readable_for?(current_user) + @message.add_recipients [original_message.sender_id] + @message.group_id = original_message.group_id + @message.private = original_message.private + @message.subject = I18n.t('messages.model.reply_subject', subject: original_message.subject) + @message.body = I18n.t('messages.model.reply_header', user: original_message.sender.display, + when: I18n.l(original_message.created_at, format: :short)) + "\n" + original_message.body.each_line { |l| @message.body += I18n.t('messages.model.reply_indent', line: l) } + else + redirect_to new_message_url, alert: I18n.t('messages.new.error_private') end end @@ -33,18 +32,18 @@ def create @message = @current_user.send_messages.new(params[:message]) if @message.save DeliverMessageJob.perform_later(@message) - redirect_to messages_url, :notice => I18n.t('messages.create.notice') + redirect_to messages_url, notice: I18n.t('messages.create.notice') else - render :action => 'new' + render action: 'new' end end # Shows a single message. def show @message = Message.find(params[:id]) - unless @message.is_readable_for?(current_user) - redirect_to messages_url, alert: 'Nachricht ist privat!' - end + return if @message.is_readable_for?(current_user) + + redirect_to messages_url, alert: 'Nachricht ist privat!' end def toggle_private diff --git a/plugins/messages/app/helpers/messages_helper.rb b/plugins/messages/app/helpers/messages_helper.rb index d5371fe4..adb8fe88 100644 --- a/plugins/messages/app/helpers/messages_helper.rb +++ b/plugins/messages/app/helpers/messages_helper.rb @@ -1,11 +1,11 @@ module MessagesHelper def format_subject(message, length) if message.subject.length > length - subject = truncate(message.subject, :length => length) - body = "" + subject = truncate(message.subject, length: length) + body = '' else subject = message.subject - body = truncate(message.body, :length => length - subject.length) + body = truncate(message.body, length: length - subject.length) end "#{link_to(h(subject), message)} #{h(body)}".html_safe end diff --git a/plugins/messages/app/mail_receivers/messages_mail_receiver.rb b/plugins/messages/app/mail_receivers/messages_mail_receiver.rb index e9ca99f3..7b8a0356 100644 --- a/plugins/messages/app/mail_receivers/messages_mail_receiver.rb +++ b/plugins/messages/app/mail_receivers/messages_mail_receiver.rb @@ -1,4 +1,4 @@ -require "email_reply_trimmer" +require 'email_reply_trimmer' class MessagesMailReceiver def self.regexp @@ -9,29 +9,25 @@ def initialize(match) @message = Message.find_by_id(match[:message_id]) @user = User.find_by_id(match[:user_id]) - raise "Message could not be found" if @message.nil? - raise "User could not be found" if @user.nil? + raise 'Message could not be found' if @message.nil? + raise 'User could not be found' if @user.nil? hash = @message.mail_hash_for_user(@user) - raise "Hash does not match expectations" unless hash.casecmp(match[:hash]) == 0 + raise 'Hash does not match expectations' unless hash.casecmp(match[:hash]) == 0 end def received(data) mail = Mail.new data mail_part = get_mail_part(mail) - raise "No valid content could be found" if mail_part.nil? + raise 'No valid content could be found' if mail_part.nil? body = mail_part.body.decoded - unless mail_part.content_type_parameters.nil? - body = body.force_encoding mail_part.content_type_parameters[:charset] - end + body = body.force_encoding mail_part.content_type_parameters[:charset] unless mail_part.content_type_parameters.nil? - if MIME::Type.simplified(mail_part.content_type) == "text/html" - body = Nokogiri::HTML(body).text - end + body = Nokogiri::HTML(body).text if MIME::Type.simplified(mail_part.content_type) == 'text/html' - body.encode!(Encoding::default_internal) + body.encode!(Encoding.default_internal) body = EmailReplyTrimmer.trim(body) raise BlankBodyException if body.empty? @@ -39,16 +35,16 @@ def received(data) group: @message.group, private: @message.private, received_email: data - if @message.reply_to - message.reply_to_message = @message.reply_to_message - else - message.reply_to_message = @message - end - if mail.subject - message.subject = mail.subject.gsub("[#{FoodsoftConfig[:name]}] ", "") - else - message.subject = I18n.t('messages.model.reply_subject', subject: message.reply_to_message.subject) - end + message.reply_to_message = if @message.reply_to + @message.reply_to_message + else + @message + end + message.subject = if mail.subject + mail.subject.gsub("[#{FoodsoftConfig[:name]}] ", '') + else + I18n.t('messages.model.reply_subject', subject: message.reply_to_message.subject) + end message.add_recipients [@message.sender_id] message.save! @@ -64,16 +60,14 @@ def get_mail_part(mail) for part in mail.parts part = get_mail_part(part) content_type = MIME::Type.simplified(part.content_type) - if content_type == "text/plain" || !mail_part && content_type == "text/html" - mail_part = part - end + mail_part = part if content_type == 'text/plain' || (!mail_part && content_type == 'text/html') end mail_part end class BlankBodyException < MidiSmtpServer::SmtpdException def initialize(msg = nil) - super msg, 541, 'The recipient address rejected your message because of a blank plain body' + super(msg, 541, 'The recipient address rejected your message because of a blank plain body') end end end diff --git a/plugins/messages/app/models/message.rb b/plugins/messages/app/models/message.rb index f6b03c10..b6554322 100644 --- a/plugins/messages/app/models/message.rb +++ b/plugins/messages/app/models/message.rb @@ -1,17 +1,17 @@ -require "base32" +require 'base32' class Message < ApplicationRecord - belongs_to :sender, class_name: 'User', foreign_key: 'sender_id' - belongs_to :group, optional: true, class_name: 'Group', foreign_key: 'group_id' + belongs_to :sender, class_name: 'User' + belongs_to :group, optional: true, class_name: 'Group' belongs_to :reply_to_message, optional: true, class_name: 'Message', foreign_key: 'reply_to' has_many :message_recipients, dependent: :destroy has_many :recipients, through: :message_recipients, source: :user attr_accessor :send_method, :recipient_tokens, :order_id - scope :threads, -> { where(:reply_to => nil) } - scope :thread, ->(id) { where("id = ? OR reply_to = ?", id, id) } - scope :readable_for, ->(user) { + scope :threads, -> { where(reply_to: nil) } + scope :thread, ->(id) { where('id = ? OR reply_to = ?', id, id) } + scope :readable_for, lambda { |user| user_id = user.try(&:id) joins(:message_recipients) @@ -20,7 +20,7 @@ class Message < ApplicationRecord } validates_presence_of :message_recipients, :subject, :body - validates_length_of :subject, :in => 1..255 + validates_length_of :subject, in: 1..255 after_initialize do @recipients_ids ||= [] @@ -33,7 +33,7 @@ class Message < ApplicationRecord def create_message_recipients user_ids = @recipients_ids user_ids += User.undeleted.pluck(:id) if send_method == 'all' - user_ids += Group.find(group_id).users.pluck(:id) unless group_id.blank? + user_ids += Group.find(group_id).users.pluck(:id) if group_id.present? user_ids += Order.find(order_id).users_ordered.pluck(:id) if send_method == 'order' user_ids.uniq.each do |user_id| @@ -47,7 +47,7 @@ def add_recipients(users) end def group_id=(group_id) - group = Group.find(group_id) unless group_id.blank? + group = Group.find(group_id) if group_id.present? if group @send_method = 'workgroup' if group.type == 'Workgroup' @send_method = 'ordergroup' if group.type == 'Ordergroup' @@ -96,29 +96,29 @@ def mail_to=(user_id) def mail_hash_for_user(user) digest = Digest::SHA1.new - digest.update self.id.to_s - digest.update ":" + digest.update id.to_s + digest.update ':' digest.update salt - digest.update ":" + digest.update ':' digest.update user.id.to_s Base32.encode digest.digest end # Returns true if this message is a system message, i.e. was sent automatically by Foodsoft itself. def system_message? - self.sender_id.nil? + sender_id.nil? end def sender_name - system_message? ? I18n.t('layouts.foodsoft') : sender.display rescue "?" + system_message? ? I18n.t('layouts.foodsoft') : sender.display + rescue StandardError + '?' end - def recipients_ids - @recipients_ids - end + attr_reader :recipients_ids def last_reply - Message.where(reply_to: self.id).order(:created_at).last + Message.where(reply_to: id).order(:created_at).last end def is_readable_for?(user) @@ -135,6 +135,6 @@ def can_toggle_private?(user) private def create_salt - self.salt = [Array.new(6) { rand(256).chr }.join].pack("m").chomp + self.salt = [Array.new(6) { rand(256).chr }.join].pack('m').chomp end end diff --git a/plugins/messages/app/models/message_recipient.rb b/plugins/messages/app/models/message_recipient.rb index e205ea5b..671b557d 100644 --- a/plugins/messages/app/models/message_recipient.rb +++ b/plugins/messages/app/models/message_recipient.rb @@ -2,5 +2,5 @@ class MessageRecipient < ActiveRecord::Base belongs_to :message belongs_to :user - enum email_state: [:pending, :sent, :skipped] + enum email_state: %i[pending sent skipped] end diff --git a/plugins/messages/app/models/messagegroup.rb b/plugins/messages/app/models/messagegroup.rb index 7c7f6c03..93666dd5 100644 --- a/plugins/messages/app/models/messagegroup.rb +++ b/plugins/messages/app/models/messagegroup.rb @@ -1,5 +1,3 @@ class Messagegroup < Group validates_uniqueness_of :name - - protected end diff --git a/plugins/messages/config/routes.rb b/plugins/messages/config/routes.rb index d66eebdd..6d276428 100644 --- a/plugins/messages/config/routes.rb +++ b/plugins/messages/config/routes.rb @@ -1,13 +1,13 @@ Rails.application.routes.draw do scope '/:foodcoop' do - resources :messages, :only => [:index, :show, :new, :create] do + resources :messages, only: %i[index show new create] do member do get :thread post :toggle_private end end - resources :message_threads, :only => [:index, :show] + resources :message_threads, only: %i[index show] resources :messagegroups, only: [:index] do member do diff --git a/plugins/messages/db/migrate/20160226000000_add_email_to_message.rb b/plugins/messages/db/migrate/20160226000000_add_email_to_message.rb index 411600c7..034b023b 100644 --- a/plugins/messages/db/migrate/20160226000000_add_email_to_message.rb +++ b/plugins/messages/db/migrate/20160226000000_add_email_to_message.rb @@ -1,6 +1,6 @@ class AddEmailToMessage < ActiveRecord::Migration[4.2] def change add_column :messages, :salt, :string - add_column :messages, :received_email, :binary, :limit => 1.megabyte + add_column :messages, :received_email, :binary, limit: 1.megabyte end end diff --git a/plugins/messages/foodsoft_messages.gemspec b/plugins/messages/foodsoft_messages.gemspec index 13050bf8..e7967191 100644 --- a/plugins/messages/foodsoft_messages.gemspec +++ b/plugins/messages/foodsoft_messages.gemspec @@ -1,26 +1,26 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_messages/version" +require 'foodsoft_messages/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_messages" + s.name = 'foodsoft_messages' s.version = FoodsoftMessages::VERSION - s.authors = ["robwa"] - s.email = ["foodsoft-messages@ini.tiative.net"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Messaging plugin for foodsoft." - s.description = "Adds the ability to exchange messages to foodsoft." + s.authors = ['robwa'] + s.email = ['foodsoft-messages@ini.tiative.net'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Messaging plugin for foodsoft.' + s.description = 'Adds the ability to exchange messages to foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "base32" - s.add_dependency "deface", "~> 1.0" - s.add_dependency "email_reply_trimmer" - s.add_dependency "mail" + s.add_dependency 'rails' + s.add_dependency 'base32' + s.add_dependency 'deface', '~> 1.0' + s.add_dependency 'email_reply_trimmer' + s.add_dependency 'mail' - s.add_development_dependency "sqlite3" + s.add_development_dependency 'sqlite3' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/messages/lib/foodsoft_messages.rb b/plugins/messages/lib/foodsoft_messages.rb index b457c8f1..9fc71928 100644 --- a/plugins/messages/lib/foodsoft_messages.rb +++ b/plugins/messages/lib/foodsoft_messages.rb @@ -1,7 +1,7 @@ -require "foodsoft_messages/engine" -require "foodsoft_messages/mail_receiver" -require "foodsoft_messages/user_link" -require "deface" +require 'foodsoft_messages/engine' +require 'foodsoft_messages/mail_receiver' +require 'foodsoft_messages/user_link' +require 'deface' module FoodsoftMessages # Return whether messages are used or not. diff --git a/plugins/messages/lib/foodsoft_messages/engine.rb b/plugins/messages/lib/foodsoft_messages/engine.rb index 0f67abb7..f054ada6 100644 --- a/plugins/messages/lib/foodsoft_messages/engine.rb +++ b/plugins/messages/lib/foodsoft_messages/engine.rb @@ -12,15 +12,16 @@ def navigation(primary, context) sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) end end - unless primary[:admin].nil? - sub_nav = primary[:admin].sub_navigation - sub_nav.items << - SimpleNavigation::Item.new(primary, :messagegroups, I18n.t('navigation.admin.messagegroups'), context.admin_messagegroups_path) - # move to right before config item - if i = sub_nav.items.index(sub_nav[:config]) - sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) - end - end + return if primary[:admin].nil? + + sub_nav = primary[:admin].sub_navigation + sub_nav.items << + SimpleNavigation::Item.new(primary, :messagegroups, I18n.t('navigation.admin.messagegroups'), + context.admin_messagegroups_path) + # move to right before config item + return unless i = sub_nav.items.index(sub_nav[:config]) + + sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) end def default_foodsoft_config(cfg) diff --git a/plugins/messages/lib/foodsoft_messages/user_link.rb b/plugins/messages/lib/foodsoft_messages/user_link.rb index bfab42b6..6fcf99c4 100644 --- a/plugins/messages/lib/foodsoft_messages/user_link.rb +++ b/plugins/messages/lib/foodsoft_messages/user_link.rb @@ -8,7 +8,7 @@ def show_user_link(user = @current_user) show_user user else link_to show_user(user), new_message_path('message[mail_to]' => user.id), - :title => I18n.t('helpers.messages.write_message') + title: I18n.t('helpers.messages.write_message') end end end @@ -18,5 +18,5 @@ def show_user_link(user = @current_user) # modify existing helper ActiveSupport.on_load(:after_initialize) do - ApplicationHelper.send :include, FoodsoftMessages::UserLink + ApplicationHelper.include FoodsoftMessages::UserLink end diff --git a/plugins/messages/lib/foodsoft_messages/version.rb b/plugins/messages/lib/foodsoft_messages/version.rb index 2da75575..6209100d 100644 --- a/plugins/messages/lib/foodsoft_messages/version.rb +++ b/plugins/messages/lib/foodsoft_messages/version.rb @@ -1,3 +1,3 @@ module FoodsoftMessages - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/polls/Rakefile b/plugins/polls/Rakefile old mode 100644 new mode 100755 index 2834c5f3..861a530a --- a/plugins/polls/Rakefile +++ b/plugins/polls/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/polls/app/controllers/polls_controller.rb b/plugins/polls/app/controllers/polls_controller.rb index aac4ef0e..91934f6e 100644 --- a/plugins/polls/app/controllers/polls_controller.rb +++ b/plugins/polls/app/controllers/polls_controller.rb @@ -27,9 +27,9 @@ def create def edit @poll = Poll.find(params[:id]) - if user_has_no_right - redirect_to polls_path, alert: t('.no_right') - end + return unless user_has_no_right + + redirect_to polls_path, alert: t('.no_right') end def update @@ -53,8 +53,8 @@ def destroy @poll.destroy redirect_to polls_path, notice: t('.notice') end - rescue => error - redirect_to polls_path, alert: t('.error', error: error.message) + rescue StandardError => e + redirect_to polls_path, alert: t('.error', error: e.message) end def vote @@ -73,25 +73,25 @@ def vote @poll_vote = @poll.poll_votes.where(attributes).first_or_initialize - if request.post? - @poll_vote.update!(note: params[:note], user: current_user) + return unless request.post? - if @poll.single_select? - choices = {} - choice = params[:choice] - choices[choice] = '1' if choice - else - choices = params[:choices].try(:to_h) || {} - end + @poll_vote.update!(note: params[:note], user: current_user) - @poll_vote.poll_choices = choices.map do |choice, value| - poll_choice = @poll_vote.poll_choices.where(choice: choice).first_or_initialize - poll_choice.update!(value: value) - poll_choice - end + if @poll.single_select? + choices = {} + choice = params[:choice] + choices[choice] = '1' if choice + else + choices = params[:choices].try(:to_h) || {} + end - redirect_to @poll + @poll_vote.poll_choices = choices.map do |choice, value| + poll_choice = @poll_vote.poll_choices.where(choice: choice).first_or_initialize + poll_choice.update!(value: value) + poll_choice end + + redirect_to @poll end private diff --git a/plugins/polls/db/migrate/20181110000000_create_polls.rb b/plugins/polls/db/migrate/20181110000000_create_polls.rb index 990e75f0..120b7eef 100644 --- a/plugins/polls/db/migrate/20181110000000_create_polls.rb +++ b/plugins/polls/db/migrate/20181110000000_create_polls.rb @@ -24,14 +24,14 @@ def change t.references :ordergroup t.text :note t.timestamps - t.index [:poll_id, :user_id, :ordergroup_id], unique: true + t.index %i[poll_id user_id ordergroup_id], unique: true end create_table :poll_choices do |t| t.references :poll_vote, null: false t.integer :choice, null: false t.integer :value, null: false - t.index [:poll_vote_id, :choice], unique: true + t.index %i[poll_vote_id choice], unique: true end end end diff --git a/plugins/polls/db/migrate/20181120000000_increase_choices_size.rb b/plugins/polls/db/migrate/20181120000000_increase_choices_size.rb index d809e3ea..621863dd 100644 --- a/plugins/polls/db/migrate/20181120000000_increase_choices_size.rb +++ b/plugins/polls/db/migrate/20181120000000_increase_choices_size.rb @@ -1,5 +1,5 @@ class IncreaseChoicesSize < ActiveRecord::Migration[4.2] def up - change_column :polls, :choices, :text, limit: 65535 + change_column :polls, :choices, :text, limit: 65_535 end end diff --git a/plugins/polls/foodsoft_polls.gemspec b/plugins/polls/foodsoft_polls.gemspec index e5b25173..607a1276 100644 --- a/plugins/polls/foodsoft_polls.gemspec +++ b/plugins/polls/foodsoft_polls.gemspec @@ -1,21 +1,21 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_polls/version" +require 'foodsoft_polls/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_polls" + s.name = 'foodsoft_polls' s.version = FoodsoftPolls::VERSION - s.authors = ["paroga"] - s.email = ["paroga@paroga.com"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Polls plugin for foodsoft." - s.description = "Adds possibility to do polls with foodsoft." + s.authors = ['paroga'] + s.email = ['paroga@paroga.com'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Polls plugin for foodsoft.' + s.description = 'Adds possibility to do polls with foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "deface", "~> 1.0" + s.add_dependency 'rails' + s.add_dependency 'deface', '~> 1.0' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/polls/lib/foodsoft_polls/engine.rb b/plugins/polls/lib/foodsoft_polls/engine.rb index a76399f0..e4812345 100644 --- a/plugins/polls/lib/foodsoft_polls/engine.rb +++ b/plugins/polls/lib/foodsoft_polls/engine.rb @@ -8,9 +8,9 @@ def navigation(primary, context) sub_nav.items << SimpleNavigation::Item.new(primary, :polls, I18n.t('navigation.polls'), context.polls_path) # move to right before tasks item - if i = sub_nav.items.index(sub_nav[:tasks]) - sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) - end + return unless i = sub_nav.items.index(sub_nav[:tasks]) + + sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) end end end diff --git a/plugins/polls/lib/foodsoft_polls/version.rb b/plugins/polls/lib/foodsoft_polls/version.rb index 5f3fb96d..84369283 100644 --- a/plugins/polls/lib/foodsoft_polls/version.rb +++ b/plugins/polls/lib/foodsoft_polls/version.rb @@ -1,3 +1,3 @@ module FoodsoftPolls - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/printer/Rakefile b/plugins/printer/Rakefile old mode 100644 new mode 100755 index 1c9d9839..fbf50e1d --- a/plugins/printer/Rakefile +++ b/plugins/printer/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/printer/app/controllers/printer_controller.rb b/plugins/printer/app/controllers/printer_controller.rb index 178787da..f07ac805 100644 --- a/plugins/printer/app/controllers/printer_controller.rb +++ b/plugins/printer/app/controllers/printer_controller.rb @@ -37,9 +37,7 @@ def update_job(data) job = PrinterJob.unfinished.find_by_id(json[:id]) return unless job - if json[:state] - job.add_update! json[:state], json[:message] - end + job.add_update! json[:state], json[:message] if json[:state] job.finish! if json[:finish] end @@ -53,6 +51,7 @@ def bearer_token def authenticate_printer return head(:unauthorized) unless bearer_token - return head(:forbidden) if bearer_token != FoodsoftConfig[:printer_token] + + head(:forbidden) if bearer_token != FoodsoftConfig[:printer_token] end end diff --git a/plugins/printer/app/controllers/printer_jobs_controller.rb b/plugins/printer/app/controllers/printer_jobs_controller.rb index 17333fb5..da89d106 100644 --- a/plugins/printer/app/controllers/printer_jobs_controller.rb +++ b/plugins/printer/app/controllers/printer_jobs_controller.rb @@ -15,7 +15,7 @@ def create state = order.open? ? 'queued' : 'ready' count = 0 PrinterJob.transaction do - %w(articles fax groups matrix).each do |document| + %w[articles fax groups matrix].each do |document| next unless FoodsoftConfig["printer_print_order_#{document}"] job = PrinterJob.create! order: order, document: document, created_by: current_user @@ -39,7 +39,7 @@ def destroy job = PrinterJob.find(params[:id]) job.finish! current_user redirect_to printer_jobs_path, notice: t('.notice') - rescue => error - redirect_to printer_jobs_path, t('errors.general_msg', msg: error.message) + rescue StandardError => e + redirect_to printer_jobs_path, t('errors.general_msg', msg: e.message) end end diff --git a/plugins/printer/config/routes.rb b/plugins/printer/config/routes.rb index 5b1a4784..84a634c5 100644 --- a/plugins/printer/config/routes.rb +++ b/plugins/printer/config/routes.rb @@ -4,7 +4,7 @@ get :socket, on: :collection end - resources :printer_jobs, only: [:index, :create, :show, :destroy] do + resources :printer_jobs, only: %i[index create show destroy] do get :document, on: :member end end diff --git a/plugins/printer/db/migrate/20181201000000_create_printer_jobs.rb b/plugins/printer/db/migrate/20181201000000_create_printer_jobs.rb index ee7665e4..36d175c5 100644 --- a/plugins/printer/db/migrate/20181201000000_create_printer_jobs.rb +++ b/plugins/printer/db/migrate/20181201000000_create_printer_jobs.rb @@ -15,6 +15,6 @@ def change t.text :message end - add_index :printer_job_updates, [:printer_job_id, :created_at] + add_index :printer_job_updates, %i[printer_job_id created_at] end end diff --git a/plugins/printer/foodsoft_printer.gemspec b/plugins/printer/foodsoft_printer.gemspec index cf249593..a6e54455 100644 --- a/plugins/printer/foodsoft_printer.gemspec +++ b/plugins/printer/foodsoft_printer.gemspec @@ -1,22 +1,22 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_printer/version" +require 'foodsoft_printer/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_printer" + s.name = 'foodsoft_printer' s.version = FoodsoftPrinter::VERSION - s.authors = ["paroga"] - s.email = ["paroga@paroga.com"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Printer plugin for foodsoft." - s.description = "Add a printer queue to foodsoft." + s.authors = ['paroga'] + s.email = ['paroga@paroga.com'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Printer plugin for foodsoft.' + s.description = 'Add a printer queue to foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" - s.add_dependency "deface", "~> 1.0" - s.add_dependency "tubesock" + s.add_dependency 'rails' + s.add_dependency 'deface', '~> 1.0' + s.add_dependency 'tubesock' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/printer/lib/foodsoft_printer/engine.rb b/plugins/printer/lib/foodsoft_printer/engine.rb index 22144e30..8f1f00cc 100644 --- a/plugins/printer/lib/foodsoft_printer/engine.rb +++ b/plugins/printer/lib/foodsoft_printer/engine.rb @@ -3,18 +3,19 @@ class Engine < ::Rails::Engine def navigation(primary, context) return unless FoodsoftPrinter.enabled? - unless primary[:orders].nil? - sub_nav = primary[:orders].sub_navigation - sub_nav.items << - SimpleNavigation::Item.new(primary, :printer_jobs, I18n.t('navigation.orders.printer_jobs'), context.printer_jobs_path) - end + return if primary[:orders].nil? + + sub_nav = primary[:orders].sub_navigation + sub_nav.items << + SimpleNavigation::Item.new(primary, :printer_jobs, I18n.t('navigation.orders.printer_jobs'), + context.printer_jobs_path) end def default_foodsoft_config(cfg) cfg[:use_printer] = false end - initializer 'foodsoft_printer.order_printer_jobs' do |app| + initializer 'foodsoft_printer.order_printer_jobs' do |_app| if Rails.configuration.cache_classes OrderPrinterJobs.install else diff --git a/plugins/printer/lib/foodsoft_printer/order_printer_jobs.rb b/plugins/printer/lib/foodsoft_printer/order_printer_jobs.rb index 7501a69e..4c7eeeaa 100644 --- a/plugins/printer/lib/foodsoft_printer/order_printer_jobs.rb +++ b/plugins/printer/lib/foodsoft_printer/order_printer_jobs.rb @@ -4,14 +4,14 @@ def self.included(base) # :nodoc: base.class_eval do has_many :printer_jobs, dependent: :destroy - alias foodsoft_printer_orig_finish! finish! + alias_method :foodsoft_printer_orig_finish!, :finish! def finish!(user) foodsoft_printer_orig_finish!(user) - unless finished? - printer_jobs.unfinished.each do |job| - job.add_update! 'ready' - end + return if finished? + + printer_jobs.unfinished.each do |job| + job.add_update! 'ready' end end end diff --git a/plugins/printer/lib/foodsoft_printer/version.rb b/plugins/printer/lib/foodsoft_printer/version.rb index 17bd39cb..e9d2ad84 100644 --- a/plugins/printer/lib/foodsoft_printer/version.rb +++ b/plugins/printer/lib/foodsoft_printer/version.rb @@ -1,3 +1,3 @@ module FoodsoftPrinter - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/uservoice/foodsoft_uservoice.gemspec b/plugins/uservoice/foodsoft_uservoice.gemspec index f33760fb..4defe395 100644 --- a/plugins/uservoice/foodsoft_uservoice.gemspec +++ b/plugins/uservoice/foodsoft_uservoice.gemspec @@ -1,20 +1,21 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_uservoice/version" +require 'foodsoft_uservoice/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_uservoice" + s.name = 'foodsoft_uservoice' s.version = FoodsoftUservoice::VERSION - s.authors = ["wvengen"] - s.email = ["dev-foodsoft@willem.engen.nl"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Uservoice plugin for foodsoft." - s.description = "Adds a uservoice feedback button to foodsoft." + s.authors = ['wvengen'] + s.email = ['dev-foodsoft@willem.engen.nl'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Uservoice plugin for foodsoft.' + s.description = 'Adds a uservoice feedback button to foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["README.md"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['README.md'] - s.add_dependency "rails" - s.add_dependency "content_for_in_controllers" + s.add_dependency 'rails' + s.add_dependency 'content_for_in_controllers' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/uservoice/lib/foodsoft_uservoice.rb b/plugins/uservoice/lib/foodsoft_uservoice.rb index b4718445..19b52ddc 100644 --- a/plugins/uservoice/lib/foodsoft_uservoice.rb +++ b/plugins/uservoice/lib/foodsoft_uservoice.rb @@ -1,5 +1,5 @@ -require "content_for_in_controllers" -require "foodsoft_uservoice/engine" +require 'content_for_in_controllers' +require 'foodsoft_uservoice/engine' module FoodsoftUservoice # enabled when configured, but can still be disabled by use_uservoice option @@ -19,11 +19,11 @@ def add_uservoice_script # include uservoice javascript api_key = FoodsoftConfig[:uservoice]['api_key'] - js_pre = "UserVoice=window.UserVoice||[];" + js_pre = 'UserVoice=window.UserVoice||[];' js_load = "var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/#{view_context.j api_key}.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s);" # configuration - sections = FoodsoftConfig[:uservoice].reject { |k, v| k == 'api_key' } + sections = FoodsoftConfig[:uservoice].reject { |k, _v| k == 'api_key' } sections.each_pair do |k, v| if k == 'identify' v['id'] = current_user.try(:id) if v.include?('id') @@ -48,5 +48,5 @@ def add_uservoice_script end ActiveSupport.on_load(:after_initialize) do - ApplicationController.send :include, FoodsoftUservoice::LoadUservoice + ApplicationController.include FoodsoftUservoice::LoadUservoice end diff --git a/plugins/uservoice/lib/foodsoft_uservoice/version.rb b/plugins/uservoice/lib/foodsoft_uservoice/version.rb index 8d78e3de..e806ff1d 100644 --- a/plugins/uservoice/lib/foodsoft_uservoice/version.rb +++ b/plugins/uservoice/lib/foodsoft_uservoice/version.rb @@ -1,3 +1,3 @@ module FoodsoftUservoice - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/wiki/Rakefile b/plugins/wiki/Rakefile old mode 100644 new mode 100755 index 5d2e31db..dd14bed8 --- a/plugins/wiki/Rakefile +++ b/plugins/wiki/Rakefile @@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' Bundler::GemHelper.install_tasks @@ -34,4 +34,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end -task :default => :test +task default: :test diff --git a/plugins/wiki/app/controllers/pages_controller.rb b/plugins/wiki/app/controllers/pages_controller.rb index c065abe7..4a8c9a5f 100644 --- a/plugins/wiki/app/controllers/pages_controller.rb +++ b/plugins/wiki/app/controllers/pages_controller.rb @@ -1,20 +1,20 @@ class PagesController < ApplicationController before_action -> { require_plugin_enabled FoodsoftWiki } - before_action :catch_special_pages, only: [:show, :new] + before_action :catch_special_pages, only: %i[show new] - skip_before_action :authenticate, :only => :all - before_action :only => :all do - authenticate_or_token(['wiki', 'all']) + skip_before_action :authenticate, only: :all + before_action only: :all do + authenticate_or_token(%w[wiki all]) end before_action do content_for :head, view_context.rss_meta_tag end def index - @page = Page.find_by_permalink "Home" + @page = Page.find_by_permalink 'Home' if @page - render :action => 'show' + render action: 'show' else redirect_to all_pages_path end @@ -34,11 +34,11 @@ def show end if @page.nil? - redirect_to new_page_path(:title => params[:permalink]) + redirect_to new_page_path(title: params[:permalink]) elsif @page.redirect? page = Page.find_by_id(@page.redirect) unless page.nil? - flash[:notice] = I18n.t('pages.cshow.redirect_notice', :page => @page.title) + flash[:notice] = I18n.t('pages.cshow.redirect_notice', page: @page.title) redirect_to wiki_page_path(page.permalink) end end @@ -46,12 +46,12 @@ def show def new @page = Page.new - @page.title = params[:title].gsub("_", " ") if params[:title] + @page.title = params[:title].gsub('_', ' ') if params[:title] @page.parent = Page.find_by_permalink(params[:parent]) if params[:parent] respond_to do |format| format.html # new.html.erb - format.xml { render :xml => @page } + format.xml { render xml: @page } end end @@ -60,36 +60,32 @@ def edit end def create - @page = Page.new(params[:page].merge({ :user => current_user })) + @page = Page.new(params[:page].merge({ user: current_user })) if params[:preview] - render :action => 'new' + render action: 'new' + elsif @page.save + flash[:notice] = I18n.t('pages.create.notice') + redirect_to(wiki_page_path(@page.permalink)) else - if @page.save - flash[:notice] = I18n.t('pages.create.notice') - redirect_to(wiki_page_path(@page.permalink)) - else - render :action => "new" - end + render action: 'new' end end def update @page = Page.find(params[:id]) - @page.attributes = params[:page].merge({ :user => current_user }) + @page.attributes = params[:page].merge({ user: current_user }) if params[:preview] @page.attributes = params[:page] - render :action => 'edit' + render action: 'edit' + elsif @page.save + @page.parent_id = parent_id if params[:parent_id].present? \ + && params[:parent_id] != @page_id + flash[:notice] = I18n.t('pages.update.notice') + redirect_to wiki_page_path(@page.permalink) else - if @page.save - @page.parent_id = parent_id if (!params[:parent_id].blank? \ - && params[:parent_id] != @page_id) - flash[:notice] = I18n.t('pages.update.notice') - redirect_to wiki_page_path(@page.permalink) - else - render :action => "edit" - end + render action: 'edit' end rescue ActiveRecord::StaleObjectError flash[:error] = I18n.t('pages.error_stale_object') @@ -100,7 +96,7 @@ def destroy @page = Page.find(params[:id]) @page.destroy - flash[:notice] = I18n.t('pages.destroy.notice', :page => @page.title) + flash[:notice] = I18n.t('pages.destroy.notice', page: @page.title) redirect_to wiki_path end @@ -109,23 +105,23 @@ def all @partial = params[:view] || 'site_map' if params[:name] - @pages = @pages.where("title LIKE ?", "%#{params[:name]}%").limit(20) + @pages = @pages.where('title LIKE ?', "%#{params[:name]}%").limit(20) @partial = 'title_list' end - if params[:sort] - sort = case params[:sort] - when "title" then "title" - when "title_reverse" then "title DESC" - when "last_updated" then "updated_at DESC" - when "last_updated_reverse" then "updated_at" + sort = if params[:sort] + case params[:sort] + when 'title' then 'title' + when 'title_reverse' then 'title DESC' + when 'last_updated' then 'updated_at DESC' + when 'last_updated_reverse' then 'updated_at' end - else - sort = "title" - end + else + 'title' + end @pages = @pages.order(sort) respond_to do |format| format.html - format.rss { render :layout => false } + format.rss { render layout: false } end end @@ -150,15 +146,13 @@ def revert def variables keys = Foodsoft::ExpansionVariables.variables.keys - @variables = Hash[keys.map { |k| [k, Foodsoft::ExpansionVariables.get(k)] }] + @variables = keys.index_with { |k| Foodsoft::ExpansionVariables.get(k) } render 'variables' end private def catch_special_pages - if params[:id] == 'Help:Foodsoft_variables' - variables - end + variables if params[:id] == 'Help:Foodsoft_variables' end end diff --git a/plugins/wiki/app/helpers/pages_helper.rb b/plugins/wiki/app/helpers/pages_helper.rb index 551a169d..60ab988a 100644 --- a/plugins/wiki/app/helpers/pages_helper.rb +++ b/plugins/wiki/app/helpers/pages_helper.rb @@ -2,70 +2,70 @@ module PagesHelper include WikiCloth def rss_meta_tag - tag('link', :rel => "alternate", :type => "application/rss+xml", :title => "RSS", :href => all_pages_rss_url).html_safe + tag.link(rel: 'alternate', type: 'application/rss+xml', title: 'RSS', href: all_pages_rss_url).html_safe end def wikified_body(body, title = nil) FoodsoftWiki::WikiParser.new(data: body + "\n", params: { referer: title }).to_html(noedit: true).html_safe - rescue => e + rescue StandardError => e # try the following with line breaks: === one === == two == = three = content_tag :span, class: 'alert alert-error' do - I18n.t '.wikicloth_exception', :msg => e + I18n.t '.wikicloth_exception', msg: e end.html_safe end def link_to_wikipage(page, text = nil) - if text == nil - link_to page.title, wiki_page_path(:permalink => page.permalink) + if text.nil? + link_to page.title, wiki_page_path(permalink: page.permalink) else - link_to text, wiki_page_path(:permalink => page.permalink) + link_to text, wiki_page_path(permalink: page.permalink) end end def link_to_wikipage_by_permalink(permalink, text = nil) - unless permalink.blank? - page = Page.find_by_permalink(permalink) - if page.nil? - if text.nil? - link_to permalink, new_page_path(:title => permalink) - else - link_to text, new_page_path(:title => permalink) - end + return if permalink.blank? + + page = Page.find_by_permalink(permalink) + if page.nil? + if text.nil? + link_to permalink, new_page_path(title: permalink) else - link_to_wikipage(page, text) + link_to text, new_page_path(title: permalink) end + else + link_to_wikipage(page, text) end end def generate_toc(body) toc = String.new body.gsub(/^([=]{1,6})\s*(.*?)\s*(\1)/) do - number = $1.length - 1 - name = $2 + number = ::Regexp.last_match(1).length - 1 + name = ::Regexp.last_match(2) - toc << "*" * number + " #{name}\n" + toc << (('*' * number) + " #{name}\n") end - unless toc.blank? - FoodsoftWiki::WikiParser.new(data: toc).to_html.gsub(/
  • ([^<>\n]*)/) do - name = $1 - anchor = name.gsub(/\s/, '_').gsub(/[^a-zA-Z_]/, '') - "
  • #{name.truncate(20)}" - end.html_safe - end + return if toc.blank? + + FoodsoftWiki::WikiParser.new(data: toc).to_html.gsub(/
  • ([^<>\n]*)/) do + name = ::Regexp.last_match(1) + anchor = name.gsub(/\s/, '_').gsub(/[^a-zA-Z_]/, '') + "
  • #{name.truncate(20)}" + end.html_safe end def parent_pages_to_select(current_page) - unless current_page.homepage? # Homepage is the page trees root! + if current_page.homepage? + [] + else # Homepage is the page trees root! Page.non_redirected.reject { |p| p == current_page || p.ancestors.include?(current_page) } - else - Array.new end end # return url for all_pages rss feed def all_pages_rss_url(options = {}) - token = TokenVerifier.new(['wiki', 'all']).generate - all_pages_url({ :format => 'rss', :token => token }.merge(options)) + token = TokenVerifier.new(%w[wiki all]).generate + all_pages_url({ format: 'rss', token: token }.merge(options)) end end diff --git a/plugins/wiki/app/models/page.rb b/plugins/wiki/app/models/page.rb index e773afa7..fae004ce 100644 --- a/plugins/wiki/app/models/page.rb +++ b/plugins/wiki/app/models/page.rb @@ -1,61 +1,62 @@ class Page < ApplicationRecord include ActsAsTree - belongs_to :user, :foreign_key => 'updated_by' + belongs_to :user, foreign_key: 'updated_by' acts_as_versioned version_column: :lock_version - self.non_versioned_columns += %w(permalink created_at title) + self.non_versioned_columns += %w[permalink created_at title] - acts_as_tree :order => "title" + acts_as_tree order: 'title' attr_accessor :old_title # Save title to create redirect page when editing title validates_presence_of :title, :body validates_uniqueness_of :permalink, :title - before_validation :set_permalink, :on => :create - before_validation :update_permalink, :on => :update + before_validation :set_permalink, on: :create + before_validation :update_permalink, on: :update after_update :create_redirect - scope :non_redirected, -> { where(:redirect => nil) } - scope :no_parent, -> { where(:parent_id => nil) } + scope :non_redirected, -> { where(redirect: nil) } + scope :no_parent, -> { where(parent_id: nil) } def self.permalink(title) - title.gsub(/[\/\.,;@\s]/, "_").gsub(/[\"\']/, "") + title.gsub(%r{[/\.,;@\s]}, '_').gsub(/[\"\']/, '') end def homepage? - permalink == "Home" + permalink == 'Home' end def self.dashboard - where(permalink: "Dashboard").first + where(permalink: 'Dashboard').first end def self.public_front_page - where(permalink: "Public_frontpage").first + where(permalink: 'Public_frontpage').first end def self.welcome_mail - where(permalink: "Welcome_mail").first + where(permalink: 'Welcome_mail').first end def set_permalink - unless title.blank? - self.permalink = Page.count == 0 ? "Home" : Page.permalink(title) - end + return if title.blank? + + self.permalink = Page.count == 0 ? 'Home' : Page.permalink(title) end def diff current = versions.latest - old = versions.where(["page_id = ? and lock_version < ?", current.page_id, current.lock_version]).order('lock_version DESC').first + old = versions.where(['page_id = ? and lock_version < ?', current.page_id, + current.lock_version]).order('lock_version DESC').first if old o = '' Diffy::Diff.new(old.body, current.body).each do |line| case line - when /^\+/ then o += "#{line.chomp}
    " unless line.chomp == "+" - when /^-/ then o += "#{line.chomp}
    " unless line.chomp == "-" + when /^\+/ then o += "#{line.chomp}
    " unless line.chomp == '+' + when /^-/ then o += "#{line.chomp}
    " unless line.chomp == '-' end end o @@ -67,19 +68,19 @@ def diff protected def update_permalink - if changed.include?("title") - set_permalink - self.old_title = changes["title"].first # Save title for creating redirect - end + return unless changed.include?('title') + + set_permalink + self.old_title = changes['title'].first # Save title for creating redirect end def create_redirect - unless old_title.blank? - Page.create :redirect => id, - :title => old_title, - :body => I18n.t('model.page.redirect', :title => title), - :permalink => Page.permalink(old_title), - :updated_by => updated_by - end + return if old_title.blank? + + Page.create redirect: id, + title: old_title, + body: I18n.t('model.page.redirect', title: title), + permalink: Page.permalink(old_title), + updated_by: updated_by end end diff --git a/plugins/wiki/app/views/pages/all.rss.builder b/plugins/wiki/app/views/pages/all.rss.builder index f7194763..e0340188 100644 --- a/plugins/wiki/app/views/pages/all.rss.builder +++ b/plugins/wiki/app/views/pages/all.rss.builder @@ -1,14 +1,14 @@ -xml.instruct! :xml, :version => "1.0" -xml.rss :version => "2.0" do +xml.instruct! :xml, version: '1.0' +xml.rss version: '2.0' do xml.channel do - xml.title FoodsoftConfig[:name] + " Wiki" - xml.description "" + xml.title FoodsoftConfig[:name] + ' Wiki' + xml.description '' xml.link FoodsoftConfig[:homepage] for page in @pages xml.item do xml.title page.title - xml.description page.diff, :type => "html" + xml.description page.diff, type: 'html' xml.author User.find_by_id(page.updated_by).try(:display) xml.pubDate page.updated_at.to_s(:rfc822) xml.link wiki_page_path(page.permalink) diff --git a/plugins/wiki/config/routes.rb b/plugins/wiki/config/routes.rb index 4ebad572..ad713366 100644 --- a/plugins/wiki/config/routes.rb +++ b/plugins/wiki/config/routes.rb @@ -1,12 +1,12 @@ Rails.application.routes.draw do scope '/:foodcoop' do resources :pages do - get :all, :on => :collection - get :version, :on => :member - get :revert, :on => :member - get :diff, :on => :member + get :all, on: :collection + get :version, on: :member + get :revert, on: :member + get :diff, on: :member end get '/wiki/:permalink' => 'pages#show', :as => 'wiki_page' # , :constraints => {:permalink => /[^\s]+/} - get '/wiki' => 'pages#show', :defaults => { :permalink => 'Home' }, :as => 'wiki' + get '/wiki' => 'pages#show', :defaults => { permalink: 'Home' }, :as => 'wiki' end end diff --git a/plugins/wiki/db/migrate/20090325175756_create_pages.rb b/plugins/wiki/db/migrate/20090325175756_create_pages.rb index 846decf8..cdd00e2b 100644 --- a/plugins/wiki/db/migrate/20090325175756_create_pages.rb +++ b/plugins/wiki/db/migrate/20090325175756_create_pages.rb @@ -4,7 +4,7 @@ def self.up t.string :title t.text :body t.string :permalink - t.integer :lock_version, :default => 0 + t.integer :lock_version, default: 0 t.integer :updated_by t.integer :redirect t.integer :parent_id diff --git a/plugins/wiki/foodsoft_wiki.gemspec b/plugins/wiki/foodsoft_wiki.gemspec index 07f0c182..58be331d 100644 --- a/plugins/wiki/foodsoft_wiki.gemspec +++ b/plugins/wiki/foodsoft_wiki.gemspec @@ -1,27 +1,27 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "foodsoft_wiki/version" +require 'foodsoft_wiki/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "foodsoft_wiki" + s.name = 'foodsoft_wiki' s.version = FoodsoftWiki::VERSION - s.authors = ["wvengen"] - s.email = ["dev-foodsoft@willem.engen.nl"] - s.homepage = "https://github.com/foodcoops/foodsoft" - s.summary = "Wiki plugin for foodsoft." - s.description = "Adds a wiki to foodsoft." + s.authors = ['wvengen'] + s.email = ['dev-foodsoft@willem.engen.nl'] + s.homepage = 'https://github.com/foodcoops/foodsoft' + s.summary = 'Wiki plugin for foodsoft.' + s.description = 'Adds a wiki to foodsoft.' - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*'] + ['Rakefile', 'README.md'] - s.add_dependency "rails" + s.add_dependency 'rails' s.add_dependency 'wikicloth' s.add_dependency 'twitter-text', '~> 1.14' # wikicloth doesn't support version 2 s.add_dependency 'acts_as_versioned' # need git version, make sure that is included in foodsoft's Gemfile - s.add_dependency "deface", "~> 1.0" + s.add_dependency 'deface', '~> 1.0' s.add_dependency 'diffy' s.add_dependency 'content_for_in_controllers' - s.add_development_dependency "sqlite3" + s.add_development_dependency 'sqlite3' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/plugins/wiki/lib/foodsoft_wiki/engine.rb b/plugins/wiki/lib/foodsoft_wiki/engine.rb index 4cc20f6a..ae2ce462 100644 --- a/plugins/wiki/lib/foodsoft_wiki/engine.rb +++ b/plugins/wiki/lib/foodsoft_wiki/engine.rb @@ -8,17 +8,17 @@ def navigation(primary, ctx) subnav.item :all_pages, I18n.t('navigation.wiki.all_pages'), ctx.all_pages_path, id: nil end # move this last added item to just after the foodcoop menu - if i = primary.items.index(primary[:foodcoop]) - primary.items.insert(i + 1, primary.items.delete_at(-1)) - end + return unless i = primary.items.index(primary[:foodcoop]) + + primary.items.insert(i + 1, primary.items.delete_at(-1)) end def default_foodsoft_config(cfg) cfg[:use_wiki] = true end - initializer "foodsoft_wiki.assets.precompile" do |app| - app.config.assets.precompile += %w(icons/feed-icon-14x14.png) + initializer 'foodsoft_wiki.assets.precompile' do |app| + app.config.assets.precompile += %w[icons/feed-icon-14x14.png] end end end diff --git a/plugins/wiki/lib/foodsoft_wiki/mailer.rb b/plugins/wiki/lib/foodsoft_wiki/mailer.rb index 83a110f1..4b7a892d 100644 --- a/plugins/wiki/lib/foodsoft_wiki/mailer.rb +++ b/plugins/wiki/lib/foodsoft_wiki/mailer.rb @@ -3,10 +3,10 @@ module Mailer def self.included(base) # :nodoc: base.class_eval do # modify user presentation link to writing a message for the user - def additonal_welcome_text(user) - if FoodsoftWiki.enabled? && (page = Page.welcome_mail) - page.body - end + def additonal_welcome_text(_user) + return unless FoodsoftWiki.enabled? && (page = Page.welcome_mail) + + page.body end end end @@ -15,5 +15,5 @@ def additonal_welcome_text(user) # modify existing helper ActiveSupport.on_load(:after_initialize) do - Mailer.send :include, FoodsoftWiki::Mailer + Mailer.include FoodsoftWiki::Mailer end diff --git a/plugins/wiki/lib/foodsoft_wiki/version.rb b/plugins/wiki/lib/foodsoft_wiki/version.rb index 2a67a94e..580ee3ed 100644 --- a/plugins/wiki/lib/foodsoft_wiki/version.rb +++ b/plugins/wiki/lib/foodsoft_wiki/version.rb @@ -1,3 +1,3 @@ module FoodsoftWiki - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/plugins/wiki/lib/foodsoft_wiki/wiki_parser.rb b/plugins/wiki/lib/foodsoft_wiki/wiki_parser.rb index 37e58465..6e14d2a8 100644 --- a/plugins/wiki/lib/foodsoft_wiki/wiki_parser.rb +++ b/plugins/wiki/lib/foodsoft_wiki/wiki_parser.rb @@ -10,7 +10,7 @@ class WikiParser < WikiCloth::Parser link_attributes_for do |page| permalink = Page.permalink(page) - if Page.exists?(:permalink => permalink) + if Page.exists?(permalink: permalink) { href: url_for(:wiki_page_path, permalink: permalink) } elsif page.include? '#' # If "Foo#Bar" does not exist then consider "Foo" with anchor. @@ -20,8 +20,8 @@ class WikiParser < WikiCloth::Parser end end - section_link do |section| - "" + section_link do |_section| + '' end def to_html(render_options = {}) @@ -41,7 +41,7 @@ def link_attributes_if_number_sign_contained_in_nonexistent(page, referer) return { href: '#' + anchor } if page.empty? permalink = Page.permalink(page) - if Page.exists?(:permalink => permalink) + if Page.exists?(permalink: permalink) { href: url_for(:wiki_page_path, permalink: permalink, anchor: anchor) } else # Do not suggest to use number signs in the title. diff --git a/script/rails b/script/rails index bd79dce5..3c234a25 100755 --- a/script/rails +++ b/script/rails @@ -1,6 +1,6 @@ #!/usr/bin/env ruby # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +APP_PATH = File.expand_path('../config/application', __dir__) +require File.expand_path('../config/boot', __dir__) require 'rails/commands' diff --git a/spec/api/v1/order_articles_spec.rb b/spec/api/v1/order_articles_spec.rb index 2639a41d..8be3b463 100644 --- a/spec/api/v1/order_articles_spec.rb +++ b/spec/api/v1/order_articles_spec.rb @@ -6,12 +6,13 @@ let(:api_scopes) { ['orders:read'] } let(:json_order_articles) { json_response['order_articles'] } - let(:json_order_article_ids) { json_order_articles.map { |joa| joa["id"] } } + let(:json_order_article_ids) { json_order_articles.map { |joa| joa['id'] } } - describe "GET :index" do - context "with param q[ordered]" do + describe 'GET :index' do + context 'with param q[ordered]' do let(:order) { create(:order, article_count: 4) } let(:order_articles) { order.order_articles } + before do order_articles[0].update_attributes! quantity: 0, tolerance: 0, units_to_order: 0 order_articles[1].update_attributes! quantity: 1, tolerance: 0, units_to_order: 0 @@ -19,35 +20,36 @@ order_articles[3].update_attributes! quantity: 0, tolerance: 0, units_to_order: 1 end - it "(unset)" do + it '(unset)' do get :index, params: { foodcoop: 'f' } expect(json_order_articles.count).to eq 4 end - it "all" do + it 'all' do get :index, params: { foodcoop: 'f', q: { ordered: 'all' } } expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id) end - it "supplier" do + it 'supplier' do get :index, params: { foodcoop: 'f', q: { ordered: 'supplier' } } - expect(json_order_article_ids).to match_array [order_articles[3].id] + expect(json_order_article_ids).to contain_exactly(order_articles[3].id) end - it "member" do + it 'member' do get :index, params: { foodcoop: 'f', q: { ordered: 'member' } } expect(json_order_articles.count).to eq 0 end - context "when ordered by user" do + context 'when ordered by user' do let(:user) { create(:user, :ordergroup) } let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } + before do create(:group_order_article, group_order: go, order_article: order_articles[1], quantity: 1) create(:group_order_article, group_order: go, order_article: order_articles[2], tolerance: 0) end - it "member" do + it 'member' do get :index, params: { foodcoop: 'f', q: { ordered: 'member' } } expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id) end diff --git a/spec/api/v1/swagger_spec.rb b/spec/api/v1/swagger_spec.rb index a481155c..5c0f043a 100644 --- a/spec/api/v1/swagger_spec.rb +++ b/spec/api/v1/swagger_spec.rb @@ -4,22 +4,22 @@ # we want to load a local file in YAML-format instead of a served JSON file class SwaggerCheckerFile < Apivore::SwaggerChecker def fetch_swagger! - YAML.load(File.read(swagger_path)) + YAML.load_file(swagger_path) end end -describe 'API v1', type: :apivore, order: :defined do +describe 'API v1', order: :defined, type: :apivore do include ApiHelper - subject { SwaggerCheckerFile.instance_for Rails.root.join('doc', 'swagger.v1.yml') } + subject { SwaggerCheckerFile.instance_for Rails.root.join('doc/swagger.v1.yml') } context 'has valid paths' do context 'user' do let(:api_scopes) { ['user:read'] } # create multiple users to make sure we're getting the authenticated user, not just any - let!(:other_user_1) { create :user } - let!(:user) { create :user } - let!(:other_user_2) { create :user } + let!(:other_user_1) { create(:user) } + let!(:user) { create(:user) } + let!(:other_user_2) { create(:user) } it { is_expected.to validate(:get, '/user', 200, api_auth) } it { is_expected.to validate(:get, '/user', 401) } @@ -29,7 +29,7 @@ def fetch_swagger! context 'user/financial_overview' do let(:api_scopes) { ['finance:user'] } - let!(:user) { create :user, :ordergroup } + let!(:user) { create(:user, :ordergroup) } it { is_expected.to validate(:get, '/user/financial_overview', 200, api_auth) } it { is_expected.to validate(:get, '/user/financial_overview', 401) } @@ -39,26 +39,39 @@ def fetch_swagger! context 'user/financial_transactions' do let(:api_scopes) { ['finance:user'] } - let(:other_user) { create :user, :ordergroup } - let!(:other_ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup } + let(:other_user) { create(:user, :ordergroup) } + let!(:other_ft_1) { create(:financial_transaction, ordergroup: other_user.ordergroup) } context 'without ordergroup' do it { is_expected.to validate(:get, '/user/financial_transactions', 403, api_auth) } - it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 403, api_auth({ 'id' => other_ft_1.id })) } + + it { + expect(subject).to validate(:get, '/user/financial_transactions/{id}', 403, api_auth({ 'id' => other_ft_1.id })) + } end context 'with ordergroup' do - let(:user) { create :user, :ordergroup } - let!(:ft_1) { create :financial_transaction, ordergroup: user.ordergroup } - let!(:ft_2) { create :financial_transaction, ordergroup: user.ordergroup } - let!(:ft_3) { create :financial_transaction, ordergroup: user.ordergroup } + let(:user) { create(:user, :ordergroup) } + let!(:ft_1) { create(:financial_transaction, ordergroup: user.ordergroup) } + let!(:ft_2) { create(:financial_transaction, ordergroup: user.ordergroup) } + let!(:ft_3) { create(:financial_transaction, ordergroup: user.ordergroup) } + + let(:create_params) do + { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id, + note: 'note' } } } + end it { is_expected.to validate(:get, '/user/financial_transactions', 200, api_auth) } it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 200, api_auth({ 'id' => ft_2.id })) } - it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => other_ft_1.id })) } - it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => FinancialTransaction.last.id + 1 })) } - let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id, note: 'note' } } } } + it { + expect(subject).to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => other_ft_1.id })) + } + + it { + expect(subject).to validate(:get, '/user/financial_transactions/{id}', 404, + api_auth({ 'id' => FinancialTransaction.last.id + 1 })) + } context 'without using self service' do it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) } @@ -70,19 +83,25 @@ def fetch_swagger! it { is_expected.to validate(:post, '/user/financial_transactions', 200, api_auth(create_params)) } context 'with invalid financial transaction type' do - let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: -1, note: 'note' } } } } + let(:create_params) do + { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: -1, note: 'note' } } } + end it { is_expected.to validate(:post, '/user/financial_transactions', 404, api_auth(create_params)) } end context 'without note' do - let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id } } } } + let(:create_params) do + { '_data' => { financial_transaction: { amount: 1, + financial_transaction_type_id: ft_1.financial_transaction_type.id } } } + end it { is_expected.to validate(:post, '/user/financial_transactions', 422, api_auth(create_params)) } end context 'without enough balance' do before { FoodsoftConfig[:minimum_balance] = 1000 } + it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) } end end @@ -97,10 +116,14 @@ def fetch_swagger! let(:api_scopes) { ['group_orders:user'] } let(:order) { create(:order, article_count: 2) } - let(:user_2) { create :user, :ordergroup } + let(:user_2) { create(:user, :ordergroup) } let(:group_order_2) { create(:group_order, order: order, ordergroup: user_2.ordergroup) } - let!(:goa_2) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order_2 } - before { group_order_2.update_price!; user_2.ordergroup.update_stats! } + let!(:goa_2) { create(:group_order_article, order_article: order.order_articles[0], group_order: group_order_2) } + + before do + group_order_2.update_price! + user_2.ordergroup.update_stats! + end context 'without ordergroup' do it { is_expected.to validate(:get, '/user/group_order_articles', 403, api_auth) } @@ -108,31 +131,46 @@ def fetch_swagger! end context 'with ordergroup' do - let(:user) { create :user, :ordergroup } + let(:user) { create(:user, :ordergroup) } + let(:update_params) do + { 'id' => goa.id, '_data' => { group_order_article: { quantity: goa.quantity + 1, tolerance: 0 } } } + end + let(:create_params) do + { '_data' => { group_order_article: { order_article_id: order.order_articles[1].id, quantity: 1 } } } + end let(:group_order) { create(:group_order, order: order, ordergroup: user.ordergroup) } - let!(:goa) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order } - before { group_order.update_price!; user.ordergroup.update_stats! } + let!(:goa) { create(:group_order_article, order_article: order.order_articles[0], group_order: group_order) } + + before do + group_order.update_price! + user.ordergroup.update_stats! + end it { is_expected.to validate(:get, '/user/group_order_articles', 200, api_auth) } it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) } it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({ 'id' => goa_2.id })) } - it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({ 'id' => GroupOrderArticle.last.id + 1 })) } - let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[1].id, quantity: 1 } } } } - let(:update_params) { { 'id' => goa.id, '_data' => { group_order_article: { quantity: goa.quantity + 1, tolerance: 0 } } } } + it { + expect(subject).to validate(:get, '/user/group_order_articles/{id}', 404, + api_auth({ 'id' => GroupOrderArticle.last.id + 1 })) + } it { is_expected.to validate(:post, '/user/group_order_articles', 200, api_auth(create_params)) } it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 200, api_auth(update_params)) } it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) } context 'with an existing group_order_article' do - let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[0].id, quantity: 1 } } } } + let(:create_params) do + { '_data' => { group_order_article: { order_article_id: order.order_articles[0].id, quantity: 1 } } } + end it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) } end context 'with invalid parameter values' do - let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[0].id, quantity: -1 } } } } + let(:create_params) do + { '_data' => { group_order_article: { order_article_id: order.order_articles[0].id, quantity: -1 } } } + end let(:update_params) { { 'id' => goa.id, '_data' => { group_order_article: { quantity: -1, tolerance: 0 } } } } it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) } @@ -149,6 +187,7 @@ def fetch_swagger! context 'without enough balance' do before { FoodsoftConfig[:minimum_balance] = 1000 } + it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) } it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) } it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) } @@ -156,6 +195,7 @@ def fetch_swagger! context 'without enough apple points' do before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } + it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) } it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) } it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) } @@ -165,7 +205,9 @@ def fetch_swagger! it_handles_invalid_token_and_scope(:post, '/user/group_order_articles', -> { api_auth(create_params) }) it_handles_invalid_token_and_scope(:get, '/user/group_order_articles/{id}', -> { api_auth({ 'id' => goa.id }) }) it_handles_invalid_token_and_scope(:patch, '/user/group_order_articles/{id}', -> { api_auth(update_params) }) - it_handles_invalid_token_and_scope(:delete, '/user/group_order_articles/{id}', -> { api_auth({ 'id' => goa.id }) }) + it_handles_invalid_token_and_scope(:delete, '/user/group_order_articles/{id}', lambda { + api_auth({ 'id' => goa.id }) + }) end end @@ -188,16 +230,21 @@ def fetch_swagger! context 'financial_transactions' do let(:api_scopes) { ['finance:read'] } let(:user) { create(:user, :role_finance) } - let(:other_user) { create :user, :ordergroup } - let!(:ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup } - let!(:ft_2) { create :financial_transaction, ordergroup: other_user.ordergroup } + let(:other_user) { create(:user, :ordergroup) } + let!(:ft_1) { create(:financial_transaction, ordergroup: other_user.ordergroup) } + let!(:ft_2) { create(:financial_transaction, ordergroup: other_user.ordergroup) } it { is_expected.to validate(:get, '/financial_transactions', 200, api_auth) } it { is_expected.to validate(:get, '/financial_transactions/{id}', 200, api_auth({ 'id' => ft_2.id })) } - it { is_expected.to validate(:get, '/financial_transactions/{id}', 404, api_auth({ 'id' => FinancialTransaction.last.id + 1 })) } + + it { + expect(subject).to validate(:get, '/financial_transactions/{id}', 404, + api_auth({ 'id' => FinancialTransaction.last.id + 1 })) + } context 'without role_finance' do let(:user) { create(:user) } + it { is_expected.to validate(:get, '/financial_transactions', 403, api_auth) } it { is_expected.to validate(:get, '/financial_transactions/{id}', 403, api_auth({ 'id' => ft_2.id })) } end @@ -207,20 +254,23 @@ def fetch_swagger! end context 'financial_transaction_classes' do - let!(:cla_1) { create :financial_transaction_class } - let!(:cla_2) { create :financial_transaction_class } + let!(:cla_1) { create(:financial_transaction_class) } + let!(:cla_2) { create(:financial_transaction_class) } it { is_expected.to validate(:get, '/financial_transaction_classes', 200, api_auth) } it { is_expected.to validate(:get, '/financial_transaction_classes/{id}', 200, api_auth({ 'id' => cla_2.id })) } - it { is_expected.to validate(:get, '/financial_transaction_classes/{id}', 404, api_auth({ 'id' => cla_2.id + 1 })) } + + it { + expect(subject).to validate(:get, '/financial_transaction_classes/{id}', 404, api_auth({ 'id' => cla_2.id + 1 })) + } it_handles_invalid_token(:get, '/financial_transaction_classes') it_handles_invalid_token(:get, '/financial_transaction_classes/{id}', -> { api_auth({ 'id' => cla_1.id }) }) end context 'financial_transaction_types' do - let!(:tpy_1) { create :financial_transaction_type } - let!(:tpy_2) { create :financial_transaction_type } + let!(:tpy_1) { create(:financial_transaction_type) } + let!(:tpy_2) { create(:financial_transaction_type) } it { is_expected.to validate(:get, '/financial_transaction_types', 200, api_auth) } it { is_expected.to validate(:get, '/financial_transaction_types/{id}', 200, api_auth({ 'id' => tpy_2.id })) } @@ -232,7 +282,7 @@ def fetch_swagger! context 'orders' do let(:api_scopes) { ['orders:read'] } - let!(:order) { create :order } + let!(:order) { create(:order) } it { is_expected.to validate(:get, '/orders', 200, api_auth) } it { is_expected.to validate(:get, '/orders/{id}', 200, api_auth({ 'id' => order.id })) } @@ -258,8 +308,8 @@ def fetch_swagger! end context 'article_categories' do - let!(:cat_1) { create :article_category } - let!(:cat_2) { create :article_category } + let!(:cat_1) { create(:article_category) } + let!(:cat_2) { create(:article_category) } it { is_expected.to validate(:get, '/article_categories', 200, api_auth) } it { is_expected.to validate(:get, '/article_categories/{id}', 200, api_auth({ 'id' => cat_2.id })) } @@ -273,7 +323,7 @@ def fetch_swagger! # needs to be last context so it is always run at the end context 'and finally' do it 'tests all documented routes' do - is_expected.to validate_all_paths + expect(subject).to validate_all_paths end end end diff --git a/spec/api/v1/user/financial_transactions_spec.rb b/spec/api/v1/user/financial_transactions_spec.rb index a5952b67..62b50e4c 100644 --- a/spec/api/v1/user/financial_transactions_spec.rb +++ b/spec/api/v1/user/financial_transactions_spec.rb @@ -6,29 +6,29 @@ let(:user) { create(:user, :ordergroup) } let(:api_scopes) { ['finance:user'] } - let(:ftc1) { create :financial_transaction_class } - let(:ftc2) { create :financial_transaction_class } - let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 } - let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 } - let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 } + let(:ftc1) { create(:financial_transaction_class) } + let(:ftc2) { create(:financial_transaction_class) } + let(:ftt1) { create(:financial_transaction_type, financial_transaction_class: ftc1) } + let(:ftt2) { create(:financial_transaction_type, financial_transaction_class: ftc2) } + let(:ftt3) { create(:financial_transaction_type, financial_transaction_class: ftc2) } let(:amount) { rand(-100..100) } let(:note) { Faker::Lorem.sentence } let(:json_ft) { json_response['financial_transaction'] } - shared_examples "financial_transactions endpoint success" do + shared_examples 'financial_transactions endpoint success' do before { request } - it "returns status 200" do + it 'returns status 200' do expect(response.status).to eq 200 end end - shared_examples "financial_transactions create/update success" do - include_examples "financial_transactions endpoint success" + shared_examples 'financial_transactions create/update success' do + include_examples 'financial_transactions endpoint success' - it "returns the financial_transaction" do + it 'returns the financial_transaction' do expect(json_ft['id']).to be_present expect(json_ft['financial_transaction_type_id']).to eq ftt1.id expect(json_ft['financial_transaction_type_name']).to eq ftt1.name @@ -37,7 +37,7 @@ expect(json_ft['user_id']).to eq user.id end - it "updates the financial_transaction" do + it 'updates the financial_transaction' do resulting_ft = FinancialTransaction.where(id: json_ft['id']).first expect(resulting_ft).to be_present expect(resulting_ft.financial_transaction_type).to eq ftt1 @@ -47,58 +47,62 @@ end end - shared_examples "financial_transactions endpoint failure" do |status| + shared_examples 'financial_transactions endpoint failure' do |status| it "returns status #{status}" do request expect(response.status).to eq status end - it "does not change the ordergroup" do - expect { request }.to_not change { + it 'does not change the ordergroup' do + expect { request }.not_to(change do user.ordergroup.attributes - } + end) end - it "does not change the financial_transactions of ordergroup" do - expect { request }.to_not change { + it 'does not change the financial_transactions of ordergroup' do + expect { request }.not_to(change do user.ordergroup.financial_transactions.count - } + end) end end - describe "POST :create" do + describe 'POST :create' do let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id, note: note } } let(:request) { post :create, params: { financial_transaction: ft_params, foodcoop: 'f' } } context 'without using self service' do - include_examples "financial_transactions endpoint failure", 403 + include_examples 'financial_transactions endpoint failure', 403 end context 'with using self service' do before { FoodsoftConfig[:use_self_service] = true } - context "with no existing financial transaction" do - include_examples "financial_transactions create/update success" + context 'with no existing financial transaction' do + include_examples 'financial_transactions create/update success' end - context "with existing financial transaction" do + context 'with existing financial transaction' do before { user.ordergroup.add_financial_transaction! 5000, 'for ordering', user, ftt3 } - include_examples "financial_transactions create/update success" + + include_examples 'financial_transactions create/update success' end - context "with invalid financial transaction type" do + context 'with invalid financial transaction type' do let(:ft_params) { { amount: amount, financial_transaction_type_id: -1, note: note } } - include_examples "financial_transactions endpoint failure", 404 + + include_examples 'financial_transactions endpoint failure', 404 end - context "without note" do + context 'without note' do let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id } } - include_examples "financial_transactions endpoint failure", 422 + + include_examples 'financial_transactions endpoint failure', 422 end context 'without enough balance' do before { FoodsoftConfig[:minimum_balance] = 1000 } - include_examples "financial_transactions endpoint failure", 403 + + include_examples 'financial_transactions endpoint failure', 403 end end end diff --git a/spec/api/v1/user/group_order_articles_spec.rb b/spec/api/v1/user/group_order_articles_spec.rb index 27fbe843..d440d60b 100644 --- a/spec/api/v1/user/group_order_articles_spec.rb +++ b/spec/api/v1/user/group_order_articles_spec.rb @@ -4,6 +4,8 @@ describe Api::V1::User::GroupOrderArticlesController, type: :controller do include ApiOAuth let(:user) { create(:user, :ordergroup) } + let(:json_goa) { json_response['group_order_article'] } + let(:json_oa) { json_response['order_article'] } let(:api_scopes) { ['group_orders:user'] } let(:order) { create(:order, article_count: 1) } @@ -13,46 +15,50 @@ let(:other_tolerance) { rand(1..10) } let(:user_other) { create(:user, :ordergroup) } let!(:go_other) { create(:group_order, order: order, ordergroup: user_other.ordergroup) } - let!(:goa_other) { create(:group_order_article, group_order: go_other, order_article: oa_1, quantity: other_quantity, tolerance: other_tolerance) } - before { go_other.update_price!; user_other.ordergroup.update_stats! } + let!(:goa_other) do + create(:group_order_article, group_order: go_other, order_article: oa_1, quantity: other_quantity, + tolerance: other_tolerance) + end - let(:json_goa) { json_response['group_order_article'] } - let(:json_oa) { json_response['order_article'] } + before do + go_other.update_price! + user_other.ordergroup.update_stats! + end - shared_examples "group_order_articles endpoint success" do + shared_examples 'group_order_articles endpoint success' do before { request } - it "returns status 200" do + it 'returns status 200' do expect(response.status).to eq 200 end - it "returns the order_article" do + it 'returns the order_article' do expect(json_oa['id']).to eq oa_1.id expect(json_oa['quantity']).to eq new_quantity + other_quantity expect(json_oa['tolerance']).to eq new_tolerance + other_tolerance end - it "updates the group_order" do + it 'updates the group_order' do go = nil - expect { + expect do request go = user.ordergroup.group_orders.where(order: order).last - }.to change { go&.updated_by }.to(user) - .and change { go&.price } + end.to change { go&.updated_by }.to(user) + .and(change { go&.price }) end end - shared_examples "group_order_articles create/update success" do - include_examples "group_order_articles endpoint success" + shared_examples 'group_order_articles create/update success' do + include_examples 'group_order_articles endpoint success' - it "returns the group_order_article" do + it 'returns the group_order_article' do expect(json_goa['id']).to be_present expect(json_goa['order_article_id']).to eq oa_1.id expect(json_goa['quantity']).to eq new_quantity expect(json_goa['tolerance']).to eq new_tolerance end - it "updates the group_order_article" do + it 'updates the group_order_article' do resulting_goa = GroupOrderArticle.where(id: json_goa['id']).first expect(resulting_goa).to be_present expect(resulting_goa.quantity).to eq new_quantity @@ -60,147 +66,170 @@ end end - shared_examples "group_order_articles endpoint failure" do |status| + shared_examples 'group_order_articles endpoint failure' do |status| it "returns status #{status}" do request expect(response.status).to eq status end - it "does not change the group_order" do - expect { request }.to_not change { + it 'does not change the group_order' do + expect { request }.not_to(change do go = user.ordergroup.group_orders.where(order: order).last go&.attributes - } + end) end - it "does not change the group_order_article" do - expect { request }.to_not change { + it 'does not change the group_order_article' do + expect { request }.not_to(change do goa = GroupOrderArticle.joins(:group_order) .where(order_article_id: oa_1.id, group_orders: { ordergroup: user.ordergroup }).last goa&.attributes - } + end) end end - describe "POST :create" do + describe 'POST :create' do let(:new_quantity) { rand(1..10) } let(:new_tolerance) { rand(1..10) } let(:goa_params) { { order_article_id: oa_1.id, quantity: new_quantity, tolerance: new_tolerance } } let(:request) { post :create, params: { group_order_article: goa_params, foodcoop: 'f' } } - context "with no existing group_order" do - include_examples "group_order_articles create/update success" + context 'with no existing group_order' do + include_examples 'group_order_articles create/update success' end - context "with an existing group_order" do + context 'with an existing group_order' do let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } - include_examples "group_order_articles create/update success" + + include_examples 'group_order_articles create/update success' end - context "with an existing group_order_article" do + context 'with an existing group_order_article' do let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 0, tolerance: 1) } - before { go.update_price!; user.ordergroup.update_stats! } - include_examples "group_order_articles endpoint failure", 422 + + before do + go.update_price! + user.ordergroup.update_stats! + end + + include_examples 'group_order_articles endpoint failure', 422 end - context "with invalid parameter values" do + context 'with invalid parameter values' do let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance } } - include_examples "group_order_articles endpoint failure", 422 + + include_examples 'group_order_articles endpoint failure', 422 end context 'with a closed order' do let(:order) { create(:order, article_count: 1, state: :finished) } - include_examples "group_order_articles endpoint failure", 404 + + include_examples 'group_order_articles endpoint failure', 404 end context 'without enough balance' do before { FoodsoftConfig[:minimum_balance] = 1000 } - include_examples "group_order_articles endpoint failure", 403 + + include_examples 'group_order_articles endpoint failure', 403 end context 'without enough apple points' do before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } - include_examples "group_order_articles endpoint failure", 403 + + include_examples 'group_order_articles endpoint failure', 403 end end - describe "PATCH :update" do + describe 'PATCH :update' do let(:new_quantity) { rand(2..10) } + let(:goa_params) { { quantity: new_quantity, tolerance: new_tolerance } } + let(:request) { patch :update, params: { id: goa.id, group_order_article: goa_params, foodcoop: 'f' } } let(:new_tolerance) { rand(2..10) } let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 1, tolerance: 0) } - before { go.update_price!; user.ordergroup.update_stats! } - let(:goa_params) { { quantity: new_quantity, tolerance: new_tolerance } } - let(:request) { patch :update, params: { id: goa.id, group_order_article: goa_params, foodcoop: 'f' } } + before do + go.update_price! + user.ordergroup.update_stats! + end - context "happy flow" do - include_examples "group_order_articles create/update success" + context 'happy flow' do + include_examples 'group_order_articles create/update success' end - context "with invalid parameter values" do + context 'with invalid parameter values' do let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance } } - include_examples "group_order_articles endpoint failure", 422 + + include_examples 'group_order_articles endpoint failure', 422 end context 'with a closed order' do let(:order) { create(:order, article_count: 1, state: :finished) } - include_examples "group_order_articles endpoint failure", 404 + + include_examples 'group_order_articles endpoint failure', 404 end context 'without enough balance' do before { FoodsoftConfig[:minimum_balance] = 1000 } - include_examples "group_order_articles endpoint failure", 403 + + include_examples 'group_order_articles endpoint failure', 403 end context 'without enough apple points' do before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } - include_examples "group_order_articles endpoint failure", 403 + + include_examples 'group_order_articles endpoint failure', 403 end end - describe "DELETE :destroy" do + describe 'DELETE :destroy' do let(:new_quantity) { 0 } + let(:request) { delete :destroy, params: { id: goa.id, foodcoop: 'f' } } let(:new_tolerance) { 0 } let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1) } - before { go.update_price!; user.ordergroup.update_stats! } - let(:request) { delete :destroy, params: { id: goa.id, foodcoop: 'f' } } + before do + go.update_price! + user.ordergroup.update_stats! + end - shared_examples "group_order_articles destroy success" do - include_examples "group_order_articles endpoint success" + shared_examples 'group_order_articles destroy success' do + include_examples 'group_order_articles endpoint success' - it "does not return the group_order_article" do + it 'does not return the group_order_article' do expect(json_goa).to be_nil end - it "deletes the group_order_article" do + it 'deletes the group_order_article' do expect(GroupOrderArticle.where(id: goa.id)).to be_empty end end - context "happy flow" do - include_examples "group_order_articles destroy success" + context 'happy flow' do + include_examples 'group_order_articles destroy success' end context 'with a closed order' do let(:order) { create(:order, article_count: 1, state: :finished) } - include_examples "group_order_articles endpoint failure", 404 + + include_examples 'group_order_articles endpoint failure', 404 end context 'without enough balance' do before { FoodsoftConfig[:minimum_balance] = 1000 } - include_examples "group_order_articles destroy success" + + include_examples 'group_order_articles destroy success' end context 'without enough apple points' do before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) } - include_examples "group_order_articles destroy success" + + include_examples 'group_order_articles destroy success' end end end diff --git a/spec/api/v1/user/ordergroup_spec.rb b/spec/api/v1/user/ordergroup_spec.rb index 32d1053a..afc764f9 100644 --- a/spec/api/v1/user/ordergroup_spec.rb +++ b/spec/api/v1/user/ordergroup_spec.rb @@ -2,26 +2,26 @@ describe Api::V1::User::OrdergroupController, type: :controller do include ApiOAuth - let(:user) { create :user, :ordergroup } + let(:user) { create(:user, :ordergroup) } let(:api_scopes) { ['finance:user'] } - let(:ftc1) { create :financial_transaction_class } - let(:ftc2) { create :financial_transaction_class } - let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 } - let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 } - let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 } + let(:ftc1) { create(:financial_transaction_class) } + let(:ftc2) { create(:financial_transaction_class) } + let(:ftt1) { create(:financial_transaction_type, financial_transaction_class: ftc1) } + let(:ftt2) { create(:financial_transaction_type, financial_transaction_class: ftc2) } + let(:ftt3) { create(:financial_transaction_type, financial_transaction_class: ftc2) } - describe "GET :financial_overview" do + describe 'GET :financial_overview' do let(:order) { create(:order, article_count: 1) } + let(:json_financial_overview) { json_response['financial_overview'] } let(:oa_1) { order.order_articles.first } let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 1, tolerance: 0) } - before { go.update_price!; user.ordergroup.update_stats! } - - let(:json_financial_overview) { json_response['financial_overview'] } before do + go.update_price! + user.ordergroup.update_stats! og = user.ordergroup og.add_financial_transaction!(-1, '-1', user, ftt1) og.add_financial_transaction!(2, '2', user, ftt1) @@ -36,12 +36,12 @@ og.add_financial_transaction!(300, '300', user, ftt3) end - it "returns correct values" do + it 'returns correct values' do get :financial_overview, params: { foodcoop: 'f' } expect(json_financial_overview['account_balance']).to eq 444 expect(json_financial_overview['available_funds']).to eq 444 - go.price - ftcs = Hash[json_financial_overview['financial_transaction_class_sums'].map { |x| [x['id'], x] }] + ftcs = json_financial_overview['financial_transaction_class_sums'].index_by { |x| x['id'] } ftcs1 = ftcs[ftc1.id] expect(ftcs1['name']).to eq ftc1.name diff --git a/spec/app_config.yml b/spec/app_config.yml index 2e146be9..e6ceada0 100644 --- a/spec/app_config.yml +++ b/spec/app_config.yml @@ -27,6 +27,16 @@ development: test: <<: *defaults + shared_lists: + adapter: mysql2 + encoding: utf8mb4 + reconnect: false + database: db_shared + pool: 5 + host: mariadb + username: root + password: secret + production: <<: *defaults diff --git a/spec/factories/article.rb b/spec/factories/article.rb index 3c85288d..c920968f 100644 --- a/spec/factories/article.rb +++ b/spec/factories/article.rb @@ -2,33 +2,35 @@ FactoryBot.define do factory :_article do - unit { Faker::Unit.unit } - price { rand(0.1..26.0).round(2) } - tax { [6, 21].sample } - deposit { rand(10) < 8 ? 0 : [0.0, 0.80, 1.20, 12.00].sample } - unit_quantity { rand(5) < 3 ? 1 : rand(1..20) } - factory :article do - sequence(:name) { |n| Faker::Lorem.words(number: rand(2..4)).join(' ') + " ##{n}" } supplier - article_category - end - factory :shared_article, class: SharedArticle do - sequence(:name) { |n| Faker::Lorem.words(number: rand(2..4)).join(' ') + " s##{n}" } - order_number { Faker::Lorem.characters(number: rand(1..12)) } - shared_supplier + transient do + article_version_count { 1 } + order_number { nil } + unit_quantity { nil } + end + + after(:create) do |article, evaluator| + create_list(:article_version, evaluator.article_version_count, article: article, + order_number: evaluator.order_number, unit_quantity: evaluator.unit_quantity) + + article.reload + end end - factory :stock_article, class: StockArticle do - sequence(:name) { |n| Faker::Lorem.words(number: rand(2..4)).join(' ') + " ##{n}" } - unit_quantity { 1 } + factory :stock_article, class: 'StockArticle' do supplier - article_category - end - end - factory :article_category do - sequence(:name) { |n| Faker::Lorem.characters(number: rand(2..12)) + " ##{n}" } + transient do + stock_article_version_count { 1 } + end + + after(:create) do |stock_article, evaluator| + create_list(:article_version, evaluator.stock_article_version_count, article: stock_article) + + stock_article.reload + end + end end end diff --git a/spec/factories/article_category.rb b/spec/factories/article_category.rb new file mode 100644 index 00000000..1c825e8e --- /dev/null +++ b/spec/factories/article_category.rb @@ -0,0 +1,7 @@ +require 'factory_bot' + +FactoryBot.define do + factory :article_category do + sequence(:name) { |n| Faker::Lorem.characters(number: rand(2..12)) + " ##{n}" } + end +end diff --git a/spec/factories/article_unit_ratio.rb b/spec/factories/article_unit_ratio.rb new file mode 100644 index 00000000..a0bbff15 --- /dev/null +++ b/spec/factories/article_unit_ratio.rb @@ -0,0 +1,10 @@ +require 'factory_bot' + +FactoryBot.define do + factory :article_unit_ratio do + unit { Faker::Unit.unit } + sort { 1 } + quantity { 1 } # TODO + article_version + end +end diff --git a/spec/factories/article_version.rb b/spec/factories/article_version.rb new file mode 100644 index 00000000..df620e10 --- /dev/null +++ b/spec/factories/article_version.rb @@ -0,0 +1,35 @@ +require 'factory_bot' +FactoryBot.define do + factory :article_version do + sequence(:name) { |n| Faker::Lorem.words(number: rand(2..4)).join(' ') + " ##{n}" } + supplier_order_unit { 'XBO' } # TODO + group_order_unit { 'XBO' } # TODO + price { rand(0.1..26.0).round(2) } + tax { [6, 21].sample } + deposit { rand(10) < 8 ? 0 : [0.0, 0.80, 1.20, 12.00].sample } + article_category + article + + transient do + article_unit_ratio_count { 1 } + unit_quantity { 1 } + end + + after(:create) do |article_version, evaluator| + unless evaluator.unit_quantity.nil? + article_version.supplier_order_unit = nil + article_version.save + end + build_list(:article_unit_ratio, evaluator.article_unit_ratio_count, + article_version: article_version) do |record, i| + if !evaluator.unit_quantity.nil? && i == 0 + record.quantity = evaluator.unit_quantity + record.unit = 'XPP' + end + record.sort = i + 1 + record.save + record.reload + end + end + end +end diff --git a/spec/factories/doorkeeper.rb b/spec/factories/doorkeeper.rb index 95de4eb6..81cefd35 100644 --- a/spec/factories/doorkeeper.rb +++ b/spec/factories/doorkeeper.rb @@ -2,12 +2,12 @@ require 'doorkeeper' FactoryBot.define do - factory :oauth2_application, class: Doorkeeper::Application do + factory :oauth2_application, class: 'Doorkeeper::Application' do name { Faker::App.name } redirect_uri { 'https://example.com:1234/app' } end - factory :oauth2_access_token, class: Doorkeeper::AccessToken do + factory :oauth2_access_token, class: 'Doorkeeper::AccessToken' do application factory: :oauth2_application end end diff --git a/spec/factories/group_order.rb b/spec/factories/group_order.rb index f7e910df..d62172ea 100644 --- a/spec/factories/group_order.rb +++ b/spec/factories/group_order.rb @@ -4,6 +4,6 @@ # requires order factory :group_order do ordergroup { create(:user, groups: [FactoryBot.create(:ordergroup)]).ordergroup } - updated_by { create :user } + updated_by { create(:user) } end end diff --git a/spec/factories/invoice.rb b/spec/factories/invoice.rb index b3e65a17..3564d977 100644 --- a/spec/factories/invoice.rb +++ b/spec/factories/invoice.rb @@ -3,9 +3,9 @@ FactoryBot.define do factory :invoice do supplier - number { rand(1..99999) } + number { rand(1..99_999) } amount { rand(0.1..26.0).round(2) } - created_by { create :user } + created_by { create(:user) } after :create do |invoice| invoice.supplier.reload diff --git a/spec/factories/order.rb b/spec/factories/order.rb index 87febae2..970bd040 100644 --- a/spec/factories/order.rb +++ b/spec/factories/order.rb @@ -3,10 +3,10 @@ FactoryBot.define do factory :order do starts { Time.now } - supplier { create :supplier, article_count: (article_count.nil? ? true : article_count) } + supplier { create(:supplier, article_count: (article_count.nil? ? true : article_count)) } article_ids { supplier.articles.map(&:id) unless supplier.nil? } - created_by { create :user } - updated_by { create :user } + created_by { create(:user) } + updated_by { create(:user) } transient do article_count { true } diff --git a/spec/factories/supplier.rb b/spec/factories/supplier.rb index c8b680fc..be082f77 100644 --- a/spec/factories/supplier.rb +++ b/spec/factories/supplier.rb @@ -10,8 +10,7 @@ article_count { 0 } end - before :create do |supplier, evaluator| - next if supplier.class == SharedSupplier + before :create do |supplier, _evaluator| next if supplier.supplier_category_id? supplier.supplier_category = create :supplier_category @@ -20,10 +19,8 @@ after :create do |supplier, evaluator| article_count = evaluator.article_count article_count = rand(1..99) if article_count == true - create_list :article, article_count, supplier: supplier + create_list(:article, article_count, supplier: supplier) end - - factory :shared_supplier, class: SharedSupplier end factory :supplier_category do diff --git a/spec/factories/user.rb b/spec/factories/user.rb index eb12196f..fec68254 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -10,21 +10,21 @@ factory :admin do sequence(:nick) { |n| "admin#{n}" } first_name { 'Administrator' } - after :create do |user, evaluator| - create :workgroup, role_admin: true, user_ids: [user.id] + after :create do |user, _evaluator| + create(:workgroup, role_admin: true, user_ids: [user.id]) end end trait :ordergroup do - after :create do |user, evaluator| - create :ordergroup, user_ids: [user.id] + after :create do |user, _evaluator| + create(:ordergroup, user_ids: [user.id]) end end - [:ordergroup, :finance, :invoices, :article_meta, :suppliers, :pickups, :orders].each do |role| - trait "role_#{role}".to_sym do - after :create do |user, evaluator| - create :workgroup, "role_#{role}" => true, user_ids: [user.id] + %i[ordergroup finance invoices article_meta suppliers pickups orders].each do |role| + trait :"role_#{role}" do + after :create do |user, _evaluator| + create(:workgroup, "role_#{role}" => true, user_ids: [user.id]) end end end @@ -42,7 +42,9 @@ sequence(:name) { |n| "Order group ##{n}" } # workaround to avoid needing to save the ordergroup # avoids e.g. error after logging in related to applebar - after :create do |group| Ordergroup.find(group.id).update_stats! end + after :create do |group| + Ordergroup.find(group.id).update_stats! + end end end end diff --git a/spec/integration/articles_spec.rb b/spec/integration/articles_spec.rb index 820317b7..c453c42a 100644 --- a/spec/integration/articles_spec.rb +++ b/spec/integration/articles_spec.rb @@ -1,12 +1,13 @@ require_relative '../spec_helper' -feature ArticlesController do - let(:user) { create :user, groups: [create(:workgroup, role_article_meta: true)] } - let (:supplier) { create :supplier } - let!(:article_category) { create :article_category } +describe ArticlesController do + let(:user) { create(:user, groups: [create(:workgroup, role_article_meta: true)]) } + let(:supplier) { create(:supplier) } + let!(:article_category) { create(:article_category) } + before { login user } - describe ':index', js: true do + describe ':index', :js do before { visit supplier_articles_path(supplier_id: supplier.id) } it 'can visit supplier articles path' do @@ -16,16 +17,16 @@ it 'can create a new article' do click_on I18n.t('articles.index.new') - expect(page).to have_selector('form#new_article') - article = FactoryBot.build :article, supplier: supplier, article_category: article_category + expect(page).to have_css('form#new_article') + article = FactoryBot.build(:article, supplier: supplier, article_category: article_category) within('#new_article') do - fill_in 'article_name', :with => article.name - fill_in 'article_unit', :with => article.unit - select article.article_category.name, :from => 'article_article_category_id' - fill_in 'article_price', :with => article.price - fill_in 'article_unit_quantity', :with => article.unit_quantity - fill_in 'article_tax', :with => article.tax - fill_in 'article_deposit', :with => article.deposit + fill_in 'article_name', with: article.name + fill_in 'article_unit', with: article.unit + select article.article_category.name, from: 'article_article_category_id' + fill_in 'article_version', with: article.price + fill_in 'article_unit_quantity', with: article.unit_quantity + fill_in 'article_tax', with: article.tax + fill_in 'article_deposit', with: article.deposit # "Element cannot be scrolled into view" error, js as workaround # find('input[type="submit"]').click page.execute_script('$("form#new_article").submit();') @@ -37,6 +38,7 @@ describe ':upload' do let(:filename) { 'foodsoft_file_02.csv' } let(:file) { Rails.root.join("spec/fixtures/#{filename}") } + before do visit upload_supplier_articles_path(supplier_id: supplier.id) attach_file 'articles_file', file @@ -45,24 +47,26 @@ Dir.glob('spec/fixtures/foodsoft_file_01.*') do |test_file| describe "can import articles from #{test_file}" do let(:file) { Rails.root.join(test_file) } + it do find('input[type="submit"]').click - expect(find("tr:nth-child(1) #new_articles__note").value).to eq "bio ◎" - expect(find("tr:nth-child(2) #new_articles__name").value).to eq "Pijnboompitten" + expect(find('tr:nth-child(1) #new_articles__note').value).to eq 'bio ◎' + expect(find('tr:nth-child(2) #new_articles__name').value).to eq 'Pijnboompitten' 4.times do |i| all("tr:nth-child(#{i + 1}) select > option")[1].select_option end find('input[type="submit"]').click - expect(page).to have_content("Pijnboompitten") + expect(page).to have_content('Pijnboompitten') expect(supplier.articles.count).to eq 4 end end end - describe "can update existing article" do - let!(:article) { create :article, supplier: supplier, name: 'Foobar', order_number: 1, unit: '250 g' } + describe 'can update existing article' do + let!(:article) { create(:article, supplier: supplier, name: 'Foobar', order_number: 1, unit: '250 g') } + it do find('input[type="submit"]').click expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes' @@ -73,34 +77,36 @@ end end - describe "handles missing data" do + describe 'handles missing data' do it do find('input[type="submit"]').click # to overview find('input[type="submit"]').click # missing category, re-show form expect(find('tr.alert')).to be_present expect(supplier.articles.count).to eq 0 - all("tr select > option")[1].select_option + all('tr select > option')[1].select_option find('input[type="submit"]').click # now it should succeed expect(supplier.articles.count).to eq 1 end end - describe "can remove an existing article" do - let!(:article) { create :article, supplier: supplier, name: 'Foobar', order_number: 99999 } + describe 'can remove an existing article' do + let!(:article) { create(:article, supplier: supplier, name: 'Foobar', order_number: 99_999) } + it do check('articles_outlist_absent') find('input[type="submit"]').click expect(find("#outlisted_articles_#{article.id}", visible: :all)).to be_present - all("tr select > option")[1].select_option + all('tr select > option')[1].select_option find('input[type="submit"]').click expect(article.reload.deleted?).to be true end end - describe "can convert units when updating" do - let!(:article) { create :article, supplier: supplier, order_number: 1, unit: '250 g' } + describe 'can convert units when updating' do + let!(:article) { create(:article, supplier: supplier, order_number: 1, unit: '250 g') } + it do check('articles_convert_units') find('input[type="submit"]').click diff --git a/spec/integration/balancing_spec.rb b/spec/integration/balancing_spec.rb index 87a7757f..78ea97ad 100644 --- a/spec/integration/balancing_spec.rb +++ b/spec/integration/balancing_spec.rb @@ -1,17 +1,18 @@ require_relative '../spec_helper' -feature 'settling an order', js: true do - let(:ftt) { create :financial_transaction_type } - let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } - let(:user) { create :user, groups: [create(:ordergroup)] } - let(:supplier) { create :supplier } - let(:article) { create :article, supplier: supplier, unit_quantity: 1 } - let(:order) { create :order, supplier: supplier, article_ids: [article.id] } # need to ref article - let(:go1) { create :group_order, order: order } - let(:go2) { create :group_order, order: order } +describe 'settling an order', :js do + let(:ftt) { create(:financial_transaction_type) } + let(:admin) { create(:user, groups: [create(:workgroup, role_finance: true)]) } + let(:user) { create(:user, groups: [create(:ordergroup)]) } + let(:supplier) { create(:supplier) } + let(:article) { create(:article, supplier: supplier, unit_quantity: 1) } + let(:order) { create(:order, supplier: supplier, article_ids: [article.id]) } # need to ref article + let(:go1) { create(:group_order, order: order) } + let(:go2) { create(:group_order, order: order) } let(:oa) { order.order_articles.find_by_article_id(article.id) } - let(:goa1) { create :group_order_article, group_order: go1, order_article: oa } - let(:goa2) { create :group_order_article, group_order: go2, order_article: oa } + let(:goa1) { create(:group_order_article, group_order: go1, order_article: oa) } + let(:goa2) { create(:group_order_article, group_order: go2, order_article: oa) } + before do goa1.update_quantities(3, 0) goa2.update_quantities(1, 0) @@ -19,6 +20,10 @@ order.finish!(admin) goa1.reload goa2.reload + login admin + visit new_finance_order_path(order_id: order.id) + visit new_finance_order_path(order_id: order.id) + login admin end it 'has correct order result' do @@ -28,25 +33,22 @@ expect(goa2.result).to eq(1) end - before { login admin } - before { visit new_finance_order_path(order_id: order.id) } - it 'has product ordered visible' do expect(page).to have_content(article.name) - expect(page).to have_selector("#order_article_#{oa.id}") + expect(page).to have_css("#order_article_#{oa.id}") end it 'shows order result' do click_link article.name - expect(page).to have_selector("#group_order_articles_#{oa.id}") + expect(page).to have_css("#group_order_articles_#{oa.id}") within("#group_order_articles_#{oa.id}") do # make sure these ordergroup names are in the list for this product expect(page).to have_content(go1.ordergroup_name) expect(page).to have_content(go2.ordergroup_name) # and that their order results match what we expect - expect(page).to have_selector("#r_#{goa1.id}") + expect(page).to have_css("#r_#{goa1.id}") expect(find("#r_#{goa1.id}").value.to_f).to eq(3) - expect(page).to have_selector("#r_#{goa2.id}") + expect(page).to have_css("#r_#{goa2.id}") expect(find("#r_#{goa2.id}").value.to_f).to eq(1) end end @@ -57,7 +59,7 @@ click_link I18n.t('ui.delete') end end - expect(page).to_not have_selector("#order_article_#{oa.id}") + expect(page).to have_no_css("#order_article_#{oa.id}") expect(OrderArticle.exists?(oa.id)).to be true oa.reload expect(oa.quantity).to eq(4) @@ -75,17 +77,17 @@ click_link I18n.t('ui.delete') end end - expect(page).to_not have_selector("#order_article_#{oa.id}") + expect(page).to have_no_css("#order_article_#{oa.id}") expect(OrderArticle.exists?(oa.id)).to be false end it 'keeps ordered quantities when GroupOrderArticle is deleted from resulting order' do click_link article.name - expect(page).to have_selector("#group_order_article_#{goa1.id}") + expect(page).to have_css("#group_order_article_#{goa1.id}") within("#group_order_article_#{goa1.id}") do click_link I18n.t('ui.delete') end - expect(page).to_not have_selector("#group_order_article_#{goa1.id}") + expect(page).to have_no_css("#group_order_article_#{goa1.id}") expect(OrderArticle.exists?(oa.id)).to be true expect(GroupOrderArticle.exists?(goa1.id)).to be true goa1.reload @@ -95,13 +97,13 @@ end it 'deletes a GroupOrderArticle with no ordered amounts' do - goa1.update_attributes({ :quantity => 0, :tolerance => 0 }) + goa1.update_attributes({ quantity: 0, tolerance: 0 }) click_link article.name - expect(page).to have_selector("#group_order_article_#{goa1.id}") + expect(page).to have_css("#group_order_article_#{goa1.id}") within("#group_order_article_#{goa1.id}") do click_link I18n.t('ui.delete') end - expect(page).to_not have_selector("#group_order_article_#{goa1.id}") + expect(page).to have_no_css("#group_order_article_#{goa1.id}") expect(OrderArticle.exists?(oa.id)).to be true expect(GroupOrderArticle.exists?(goa1.id)).to be false end @@ -111,14 +113,14 @@ click_link I18n.t('ui.edit') end within("#edit_order_article_#{oa.id}") do - find('#order_article_units_to_order').set(0) + find_by_id('order_article_units_to_order').set(0) sleep 0.25 find('input[type="submit"]').click end - expect(page).to have_selector("#order_article_#{oa.id}") + expect(page).to have_css("#order_article_#{oa.id}") # make sure it still works after reloading visit new_finance_order_path(order_id: order.id) - expect(page).to have_selector("#order_article_#{oa.id}") + expect(page).to have_css("#order_article_#{oa.id}") expect(OrderArticle.exists?(oa.id)).to be true oa.reload expect(oa.units_to_order).to eq(0) @@ -130,19 +132,19 @@ within("#group_order_articles_#{oa.id}") do click_link I18n.t('finance.balancing.group_order_articles.add_group') end - expect(page).to have_selector('form#new_group_order_article') + expect(page).to have_css('form#new_group_order_article') within('#new_group_order_article') do - select user.ordergroup.name, :from => 'group_order_article_ordergroup_id' - find('#group_order_article_result').set(8) + select user.ordergroup.name, from: 'group_order_article_ordergroup_id' + find_by_id('group_order_article_result').set(8) sleep 0.25 find('input[type="submit"]').click end - expect(page).to_not have_selector('form#new_group_order_article') + expect(page).to have_no_css('form#new_group_order_article') expect(page).to have_content(user.ordergroup.name) goa = GroupOrderArticle.last - expect(goa).to_not be_nil + expect(goa).not_to be_nil expect(goa.result).to eq 8 - expect(page).to have_selector("#group_order_article_#{goa.id}") + expect(page).to have_css("#group_order_article_#{goa.id}") expect(find("#r_#{goa.id}").value.to_f).to eq 8 end @@ -151,7 +153,7 @@ within("#group_order_articles_#{oa.id}") do find("#r_#{goa1.id}").set(5).send_keys(:tab) # tab to blur and let js update end - expect(page).to have_selector('#summaryChangedWarning') # becomes visible after request is done + expect(page).to have_css('#summaryChangedWarning') # becomes visible after request is done expect(goa1.reload.result).to eq 5 expect(find("#group_order_articles_#{oa.id} tfoot td:nth-child(3)").text.to_f).to eq 6 end @@ -161,23 +163,23 @@ within("#group_order_article_#{goa1.id}") do 4.times { find('button[data-increment]').click } end - expect(page).to have_selector('#summaryChangedWarning') # becomes visible after request is done + expect(page).to have_css('#summaryChangedWarning') # becomes visible after request is done expect(goa1.reload.result).to eq 7 expect(find("#group_order_articles_#{oa.id} tfoot td:nth-child(3)").text.to_f).to eq 8 end it 'can add an article' do - new_article = create :article, supplier: supplier - expect(page).to_not have_content(new_article.name) + new_article = create(:article, supplier: supplier) + expect(page).to have_no_content(new_article.name) click_link I18n.t('finance.balancing.edit_results_by_articles.add_article') - expect(page).to have_selector('form#new_order_article') + expect(page).to have_css('form#new_order_article') within('#new_order_article') do - find('#order_article_article_id').select(new_article.name) + find_by_id('order_article_article_id').select(new_article.name) sleep 0.25 find('input[type="submit"]').click end - expect(page).to_not have_selector('form#new_order_article') + expect(page).to have_no_css('form#new_order_article') expect(page).to have_content(new_article.name) - expect(order.order_articles.where(article_id: new_article.id)).to_not be nil + expect(order.order_articles.where(article_id: new_article.id)).not_to be_nil end end diff --git a/spec/integration/config_spec.rb b/spec/integration/config_spec.rb index 91f376dd..d7d496a3 100644 --- a/spec/integration/config_spec.rb +++ b/spec/integration/config_spec.rb @@ -1,9 +1,9 @@ require_relative '../spec_helper' -feature 'admin/configs' do +describe 'admin/configs' do let(:name) { Faker::Lorem.words(number: rand(2..4)).join(' ') } let(:email) { Faker::Internet.email } - let(:admin) { create :admin } + let(:admin) { create(:admin) } before { login admin } @@ -51,13 +51,13 @@ def get_full_config end def compact_hash_deep!(h) - h.each do |k, v| + h.each do |_k, v| if v.is_a? Hash compact_hash_deep!(v) - v.reject! { |k, v| v.blank? } + v.reject! { |_k, v| v.blank? } end end - h.reject! { |k, v| v.blank? } + h.reject! { |_k, v| v.blank? } h end end diff --git a/spec/integration/login_spec.rb b/spec/integration/login_spec.rb index 0195ec8c..e0e63ffd 100644 --- a/spec/integration/login_spec.rb +++ b/spec/integration/login_spec.rb @@ -1,18 +1,19 @@ require_relative '../spec_helper' -feature LoginController do - let(:user) { create :user } +describe LoginController do + let(:user) { create(:user) } describe 'forgot password' do before { visit forgot_password_path } + it 'is accessible' do - expect(page).to have_selector 'input[id=user_email]' + expect(page).to have_css 'input[id=user_email]' end it 'sends a reset email' do fill_in 'user_email', with: user.email find('input[type=submit]').click - expect(page).to have_selector '.alert-success' + expect(page).to have_css '.alert-success' email = ActionMailer::Base.deliveries.first expect(email.to).to eq [user.email] end @@ -21,18 +22,22 @@ describe 'and reset it' do let(:token) { user.reset_password_token } let(:newpass) { user.new_random_password } - before { user.request_password_reset! } - before { visit new_password_path(id: user.id, token: token) } + + before do + user.request_password_reset! + visit new_password_path(id: user.id, token: token) + end it 'is accessible' do - expect(page).to have_selector 'input[type=password]' + expect(page).to have_css 'input[type=password]' end describe 'with wrong token' do let(:token) { 'foobar' } + it 'is not accessible' do - expect(page).to have_selector '.alert-error' - expect(page).to_not have_selector 'input[type=password]' + expect(page).to have_css '.alert-error' + expect(page).to have_no_css 'input[type=password]' end end diff --git a/spec/integration/order_spec.rb b/spec/integration/order_spec.rb index dd768997..12e027a2 100644 --- a/spec/integration/order_spec.rb +++ b/spec/integration/order_spec.rb @@ -1,12 +1,12 @@ require_relative '../spec_helper' -feature Order, js: true do - let(:admin) { create :user, groups: [create(:workgroup, role_orders: true)] } - let(:article) { create :article, unit_quantity: 1 } - let(:order) { create :order, supplier: article.supplier, article_ids: [article.id] } # need to ref article - let(:go1) { create :group_order, order: order } +describe Order, :js do + let(:admin) { create(:user, groups: [create(:workgroup, role_orders: true)]) } + let(:article) { create(:article, unit_quantity: 1) } + let(:order) { create(:order, supplier: article.supplier, article_ids: [article.id]) } # need to ref article + let(:go1) { create(:group_order, order: order) } let(:oa) { order.order_articles.find_by_article_id(article.id) } - let(:goa1) { create :group_order_article, group_order: go1, order_article: oa } + let(:goa1) { create(:group_order_article, group_order: go1, order_article: oa) } before { login admin } @@ -32,7 +32,7 @@ visit new_order_path(supplier_id: article.supplier_id) expect(page).to have_text I18n.t('orders.new.title') find('input[type="submit"]').click - expect(page).to have_selector('.alert-success') + expect(page).to have_css('.alert-success') expect(Order.count).to eq 1 expect(Order.first.supplier).to eq article.supplier end @@ -40,7 +40,7 @@ it 'can close an order' do setup_and_close_order expect(order).to be_finished - expect(page).not_to have_link I18n.t('orders.index.action_end') + expect(page).to have_no_link I18n.t('orders.index.action_end') expect(oa.units_to_order).to eq 1 end @@ -53,7 +53,7 @@ def setup_and_close_order accept_confirm do click_link_or_button I18n.t('orders.index.action_end') end - expect(page).to have_selector('.alert-success') + expect(page).to have_css('.alert-success') order.reload oa.reload end diff --git a/spec/integration/product_distribution_example_spec.rb b/spec/integration/product_distribution_example_spec.rb index e15642f1..b3e85def 100644 --- a/spec/integration/product_distribution_example_spec.rb +++ b/spec/integration/product_distribution_example_spec.rb @@ -1,12 +1,12 @@ require_relative '../spec_helper' -feature 'product distribution', js: true do - let(:ftt) { create :financial_transaction_type } - let(:admin) { create :admin } - let(:user_a) { create :user, groups: [create(:ordergroup)] } - let(:user_b) { create :user, groups: [create(:ordergroup)] } - let(:supplier) { create :supplier } - let(:article) { create :article, supplier: supplier, unit_quantity: 5 } +describe 'product distribution', :js do + let(:ftt) { create(:financial_transaction_type) } + let(:admin) { create(:admin) } + let(:user_a) { create(:user, groups: [create(:ordergroup)]) } + let(:user_b) { create(:user, groups: [create(:ordergroup)]) } + let(:supplier) { create(:supplier) } + let(:article) { create(:article, supplier: supplier, unit_quantity: 5) } let(:order) { create(:order, supplier: supplier, article_ids: [article.id]) } let(:oa) { order.order_articles.first } @@ -27,14 +27,14 @@ 2.times { find("[data-increase_quantity='#{oa.id}']").click } 3.times { find("[data-increase_tolerance='#{oa.id}']").click } find('input[type=submit]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') # gruppe b bestellt 2(0) login user_b visit new_group_order_path(order_id: order.id) scrolldown 2.times { find("[data-increase_quantity='#{oa.id}']").click } find('input[type=submit]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') # gruppe a faellt ein dass sie doch noch mehr braucht von x und aendert auf 4(1). login user_a visit edit_group_order_path(id: order.group_order(user_a.ordergroup).id, order_id: order.id) @@ -42,7 +42,7 @@ 2.times { find("[data-increase_quantity='#{oa.id}']").click } 2.times { find("[data-decrease_tolerance='#{oa.id}']").click } find('input[type=submit]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') # die zuteilung order.finish!(admin) oa.reload @@ -50,10 +50,10 @@ expect(oa.quantity).to eq(6) expect(oa.tolerance).to eq(1) # Gruppe a bekommt 3 einheiten. - goa_a = oa.group_order_articles.joins(:group_order).where(:group_orders => { :ordergroup_id => user_a.ordergroup.id }).first + goa_a = oa.group_order_articles.joins(:group_order).where(group_orders: { ordergroup_id: user_a.ordergroup.id }).first expect(goa_a.result).to eq(3) # gruppe b bekommt 2 einheiten. - goa_b = oa.group_order_articles.joins(:group_order).where(:group_orders => { :ordergroup_id => user_b.ordergroup.id }).first + goa_b = oa.group_order_articles.joins(:group_order).where(group_orders: { ordergroup_id: user_b.ordergroup.id }).first expect(goa_b.result).to eq(2) end end diff --git a/spec/integration/receive_spec.rb b/spec/integration/receive_spec.rb index 3b65107e..f1d1ec3e 100644 --- a/spec/integration/receive_spec.rb +++ b/spec/integration/receive_spec.rb @@ -1,15 +1,15 @@ require_relative '../spec_helper' -feature 'receiving an order', js: true do - let(:admin) { create :user, groups: [create(:workgroup, role_orders: true)] } - let(:supplier) { create :supplier } - let(:article) { create :article, supplier: supplier, unit_quantity: 3 } - let(:order) { create :order, supplier: supplier, article_ids: [article.id] } # need to ref article - let(:go1) { create :group_order, order: order } - let(:go2) { create :group_order, order: order } +describe 'receiving an order', :js do + let(:admin) { create(:user, groups: [create(:workgroup, role_orders: true)]) } + let(:supplier) { create(:supplier) } + let(:article) { create(:article, supplier: supplier, unit_quantity: 3) } + let(:order) { create(:order, supplier: supplier, article_ids: [article.id]) } # need to ref article + let(:go1) { create(:group_order, order: order) } + let(:go2) { create(:group_order, order: order) } let(:oa) { order.order_articles.find_by_article_id(article.id) } - let(:goa1) { create :group_order_article, group_order: go1, order_article: oa } - let(:goa2) { create :group_order_article, group_order: go2, order_article: oa } + let(:goa1) { create(:group_order_article, group_order: go1, order_article: oa) } + let(:goa2) { create(:group_order_article, group_order: go2, order_article: oa) } # set quantities of group_order_articles def set_quantities(q1, q2) @@ -40,13 +40,13 @@ def check_quantities(units, q1, q2) set_quantities [3, 0], [0, 0] visit receive_order_path(id: order.id) expect(page).to have_content(article.name) - expect(page).to have_selector("#order_article_#{oa.id}") + expect(page).to have_css("#order_article_#{oa.id}") end it 'has product not ordered invisible' do set_quantities [0, 0], [0, 0] visit receive_order_path(id: order.id) - expect(page).to_not have_selector("#order_article_#{oa.id}") + expect(page).to have_no_css("#order_article_#{oa.id}") end it 'is not received by default' do @@ -58,27 +58,27 @@ def check_quantities(units, q1, q2) it 'does not change anything when received is ordered' do set_quantities [2, 0], [3, 2] visit receive_order_path(id: order.id) - fill_in "order_articles_#{oa.id}_units_received", :with => oa.units_to_order + fill_in "order_articles_#{oa.id}_units_received", with: oa.units_to_order find('input[type="submit"]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') check_quantities 2, 2, 4 end it 'redistributes properly when received is more' do set_quantities [2, 0], [3, 2] visit receive_order_path(id: order.id) - fill_in "order_articles_#{oa.id}_units_received", :with => 3 + fill_in "order_articles_#{oa.id}_units_received", with: 3 find('input[type="submit"]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') check_quantities 3, 2, 5 end it 'redistributes properly when received is less' do set_quantities [2, 0], [3, 2] visit receive_order_path(id: order.id) - fill_in "order_articles_#{oa.id}_units_received", :with => 1 + fill_in "order_articles_#{oa.id}_units_received", with: 1 find('input[type="submit"]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') check_quantities 1, 2, 1 end @@ -96,7 +96,7 @@ def check_quantities(units, q1, q2) goa1.save! visit receive_order_path(id: order.id) find('input[type="submit"]').click - expect(page).to have_selector('body') + expect(page).to have_css('body') check_quantities 2, 3, 4 end end diff --git a/spec/integration/session_spec.rb b/spec/integration/session_spec.rb index 0838858d..bde3e622 100644 --- a/spec/integration/session_spec.rb +++ b/spec/integration/session_spec.rb @@ -1,27 +1,30 @@ require_relative '../spec_helper' -feature 'the session' do - let(:user) { create :user } +describe 'the session' do + let(:user) { create(:user) } describe 'login page' do it 'is accessible' do visit login_path - expect(page).to have_selector('input[type=password]') + expect(page).to have_css('input[type=password]') end + it 'logs me in' do login user - expect(page).to_not have_selector('.alert-error') + expect(page).to have_no_css('.alert-error') end + it 'does not log me in with wrong password' do login user.nick, 'XX' + user.password - expect(page).to have_selector('.alert-error') + expect(page).to have_css('.alert-error') end + it 'can log me in using an email address' do visit login_path - fill_in 'nick', :with => user.email - fill_in 'password', :with => user.password + fill_in 'nick', with: user.email + fill_in 'password', with: user.password find('input[type=submit]').click - expect(page).to_not have_selector('.alert-error') + expect(page).to have_no_css('.alert-error') end end end diff --git a/spec/integration/supplier_spec.rb b/spec/integration/supplier_spec.rb index cfd74e87..9710e800 100644 --- a/spec/integration/supplier_spec.rb +++ b/spec/integration/supplier_spec.rb @@ -1,21 +1,22 @@ require_relative '../spec_helper' -feature 'supplier' do - let(:supplier) { create :supplier } +describe 'supplier' do + let(:supplier) { create(:supplier) } describe 'create new' do - let(:user) { create :user, groups: [create(:workgroup, role_suppliers: true)] } + let(:user) { create(:user, groups: [create(:workgroup, role_suppliers: true)]) } + before { login user } it 'can be created' do - create :supplier_category + create(:supplier_category) visit suppliers_path click_on I18n.t('suppliers.index.action_new') - supplier = build :supplier + supplier = build(:supplier) within('#new_supplier') do - fill_in 'supplier_name', :with => supplier.name - fill_in 'supplier_address', :with => supplier.address - fill_in 'supplier_phone', :with => supplier.phone + fill_in 'supplier_name', with: supplier.name + fill_in 'supplier_address', with: supplier.address + fill_in 'supplier_phone', with: supplier.phone find('input[type="submit"]').click end expect(page).to have_content(supplier.name) diff --git a/spec/lib/article_units_lib_spec.rb b/spec/lib/article_units_lib_spec.rb new file mode 100644 index 00000000..66bcacc5 --- /dev/null +++ b/spec/lib/article_units_lib_spec.rb @@ -0,0 +1,136 @@ +require_relative '../spec_helper' + +describe ArticleUnitsLib do + it 'converts "kg" correctly' do + result = described_class.convert_old_unit('kg', 1) + expect(result).to eq({ + supplier_order_unit: 'KGM', + first_ratio: nil, + group_order_granularity: 1, + group_order_unit: 'KGM' + }) + end + + it 'converts "250g" correctly' do + result = described_class.convert_old_unit('250g', 1) + expect(result).to eq({ + supplier_order_unit: 'XPP', + first_ratio: { + unit: 'GRM', + quantity: 250.0 + }, + group_order_granularity: 1.0, + group_order_unit: 'XPP' + }) + end + + it 'converts "1/4 kg" correctly' do + result = described_class.convert_old_unit('1/4 kg', 1) + expect(result).to eq({ + supplier_order_unit: 'XPP', + first_ratio: { + unit: 'KGM', + quantity: 0.25 + }, + group_order_granularity: 1.0, + group_order_unit: 'XPP' + }) + end + + it 'converts "bunch" correctly' do + result = described_class.convert_old_unit('bunch', 1) + expect(result).to eq({ + supplier_order_unit: 'XBH', + first_ratio: nil, + group_order_granularity: 1.0, + group_order_unit: 'XBH' + }) + end + + it 'converts "jar" correctly' do + result = described_class.convert_old_unit('jar', 1) + expect(result).to eq({ + supplier_order_unit: 'XJR', + first_ratio: nil, + group_order_granularity: 1.0, + group_order_unit: 'XJR' + }) + end + + it 'converts "piece" correctly' do + result = described_class.convert_old_unit('piece', 1) + expect(result).to eq({ + supplier_order_unit: 'XPP', + first_ratio: nil, + group_order_granularity: 1.0, + group_order_unit: 'XPP' + }) + end + + it 'converts "4 piece" correctly' do + result = described_class.convert_old_unit('4 piece', 1) + expect(result).to eq({ + supplier_order_unit: 'XPK', + first_ratio: { + unit: 'XPP', + quantity: 4 + }, + group_order_granularity: 1.0, + group_order_unit: 'XPK' + }) + end + + it 'converts "1 bunch" correctly' do + result = described_class.convert_old_unit('1 bunch', 1) + expect(result).to eq({ + supplier_order_unit: 'XBH', + first_ratio: nil, + group_order_granularity: 1.0, + group_order_unit: 'XBH' + }) + end + + it 'converts "2 bunch" correctly' do + result = described_class.convert_old_unit('2 bunch', 1) + expect(result).to eq({ + supplier_order_unit: 'XPK', + first_ratio: { + unit: 'XBH', + quantity: 2 + }, + group_order_granularity: 1.0, + group_order_unit: 'XPK' + }) + end + + it 'converts "4x250g" correctly' do + result = described_class.convert_old_unit('250g', 4) + expect(result).to eq({ + supplier_order_unit: 'XPP', + first_ratio: { + unit: 'GRM', + quantity: 1000 + }, + group_order_granularity: 250, + group_order_unit: 'GRM' + }) + end + + it 'converts "6 x jar" correctly' do + result = described_class.convert_old_unit('jar', 6) + expect(result).to eq({ + supplier_order_unit: 'XPK', + first_ratio: { + unit: 'XJR', + quantity: 6 + }, + group_order_granularity: 1, + group_order_unit: 'XJR' + }) + end + + it 'fails to convert "12 nonesense"' do + result = described_class.convert_old_unit('12 nonesense', 1) + expect(result).to be_nil + end +end diff --git a/spec/lib/bank_account_information_importer_spec.rb b/spec/lib/bank_account_information_importer_spec.rb index a98441d2..928acf7f 100644 --- a/spec/lib/bank_account_information_importer_spec.rb +++ b/spec/lib/bank_account_information_importer_spec.rb @@ -1,14 +1,13 @@ require_relative '../spec_helper' describe BankTransaction do - let(:bank_account) { create :bank_account } + let(:bank_account) { create(:bank_account) } it 'empty content' do - content = <<-JSON - JSON + content = '' importer = BankAccountInformationImporter.new(bank_account) - expect(importer.import!(content)).to be(nil) + expect(importer.import!(content)).to be_nil end it 'invalid JSON' do @@ -189,7 +188,7 @@ expect(bt.date).to eq('2019-02-13'.to_date) expect(bt.text).to eq('Deutsche Bundesbahn') expect(bt.iban).to eq('DE72957284895783674747') - expect(bt.reference).to eq("743574386368 Muenchen-Hamburg 27.03.2019") + expect(bt.reference).to eq('743574386368 Muenchen-Hamburg 27.03.2019') expect(bt.receipt).to eq('Lastschrift') end @@ -241,7 +240,7 @@ expect(bt.amount).to eq(-238.68) expect(bt.date).to eq('2019-02-13'.to_date) expect(bt.text).to eq('Hammersmith Inc.') - expect(bt.iban).to be(nil) + expect(bt.iban).to be_nil expect(bt.reference).to eq('Martin Schöneicher, Inv# 123453423, Thx') expect(bt.receipt).to eq('Auslands-Überweisung') end @@ -277,8 +276,8 @@ expect(bt.amount).to eq(-12.3) expect(bt.date).to eq('2019-02-14'.to_date) expect(bt.text).to eq('superbank AG') - expect(bt.iban).to be(nil) - expect(bt.reference).to eq("Überweisung US, Wechselspesen u Provision") + expect(bt.iban).to be_nil + expect(bt.reference).to eq('Überweisung US, Wechselspesen u Provision') expect(bt.receipt).to eq('Spesen/Gebühren') end @@ -385,28 +384,28 @@ expect(bank_account.last_transaction_date).to eq('2020-01-01'.to_date) expect(bank_account.balance).to eq(22) - bt1 = bank_account.bank_transactions.find_by_external_id("T1") + bt1 = bank_account.bank_transactions.find_by_external_id('T1') expect(bt1.amount).to eq(11) expect(bt1.date).to eq('2020-01-01'.to_date) expect(bt1.text).to eq('DN1') expect(bt1.iban).to eq('DE72957284895783674747') - expect(bt1.reference).to be(nil) + expect(bt1.reference).to be_nil expect(bt1.receipt).to eq('AI1') - bt2 = bank_account.bank_transactions.find_by_external_id("T2") + bt2 = bank_account.bank_transactions.find_by_external_id('T2') expect(bt2.amount).to eq(-22) expect(bt2.date).to eq('2010-02-01'.to_date) expect(bt2.text).to eq('CN2') expect(bt2.iban).to eq('CH9300762011623852957') expect(bt2.reference).to eq('RI2') - expect(bt2.receipt).to be(nil) + expect(bt2.receipt).to be_nil - bt3 = bank_account.bank_transactions.find_by_external_id("T3") + bt3 = bank_account.bank_transactions.find_by_external_id('T3') expect(bt3.amount).to eq(33) expect(bt3.date).to eq('2000-03-01'.to_date) expect(bt3.text).to eq('DN3') - expect(bt3.iban).to be(nil) - expect(bt3.reference).to be(nil) - expect(bt3.receipt).to be(nil) + expect(bt3.iban).to be_nil + expect(bt3.reference).to be_nil + expect(bt3.receipt).to be_nil end end diff --git a/spec/lib/bank_transaction_reference_spec.rb b/spec/lib/bank_transaction_reference_spec.rb index 6d5a5490..ece0e614 100644 --- a/spec/lib/bank_transaction_reference_spec.rb +++ b/spec/lib/bank_transaction_reference_spec.rb @@ -2,94 +2,96 @@ describe BankTransactionReference do it 'returns nil for empty input' do - expect(BankTransactionReference.parse('')).to be nil + expect(BankTransactionReference.parse('')).to be_nil end it 'returns nil for invalid string' do - expect(BankTransactionReference.parse('invalid')).to be nil + expect(BankTransactionReference.parse('invalid')).to be_nil end it 'returns nil for FS1A' do - expect(BankTransactionReference.parse('FS1A')).to be nil + expect(BankTransactionReference.parse('FS1A')).to be_nil end it 'returns nil for FS1.1A' do - expect(BankTransactionReference.parse('FS1.1A')).to be nil + expect(BankTransactionReference.parse('FS1.1A')).to be_nil end it 'returns nil for xFS1A1' do - expect(BankTransactionReference.parse('xFS1A1')).to be nil + expect(BankTransactionReference.parse('xFS1A1')).to be_nil end it 'returns nil for .FS1A1' do - expect(BankTransactionReference.parse('.FS1A1')).to be nil + expect(BankTransactionReference.parse('.FS1A1')).to be_nil end it 'returns nil for FS1A1x' do - expect(BankTransactionReference.parse('FS1A1x')).to be nil + expect(BankTransactionReference.parse('FS1A1x')).to be_nil end it 'returns nil for FS1A1.' do - expect(BankTransactionReference.parse('FS1A1.')).to be nil + expect(BankTransactionReference.parse('FS1A1.')).to be_nil end it 'returns correct value for FS1A1' do - expect(BankTransactionReference.parse('FS1A1')).to match({ group: 1, parts: { "A" => 1 } }) + expect(BankTransactionReference.parse('FS1A1')).to match({ group: 1, parts: { 'A' => 1 } }) end it 'returns correct value for FS1.2A3' do - expect(BankTransactionReference.parse('FS1.2A3')).to match({ group: 1, user: 2, parts: { "A" => 3 } }) + expect(BankTransactionReference.parse('FS1.2A3')).to match({ group: 1, user: 2, parts: { 'A' => 3 } }) end it 'returns correct value for FS1A2B3C4' do - expect(BankTransactionReference.parse('FS1A2B3C4')).to match({ group: 1, parts: { "A" => 2, "B" => 3, "C" => 4 } }) + expect(BankTransactionReference.parse('FS1A2B3C4')).to match({ group: 1, parts: { 'A' => 2, 'B' => 3, 'C' => 4 } }) end it 'returns correct value for FS1A2B3A4' do - expect(BankTransactionReference.parse('FS1A2B3A4')).to match({ group: 1, parts: { "A" => 6, "B" => 3 } }) + expect(BankTransactionReference.parse('FS1A2B3A4')).to match({ group: 1, parts: { 'A' => 6, 'B' => 3 } }) end it 'returns correct value for FS1A2.34B5.67C8.90' do - expect(BankTransactionReference.parse('FS1A2.34B5.67C8.90')).to match({ group: 1, parts: { "A" => 2.34, "B" => 5.67, "C" => 8.90 } }) + expect(BankTransactionReference.parse('FS1A2.34B5.67C8.90')).to match({ group: 1, + parts: { 'A' => 2.34, 'B' => 5.67, 'C' => 8.90 } }) end it 'returns correct value for FS123A456 with comma-separated prefix' do - expect(BankTransactionReference.parse('x,FS123A456')).to match({ group: 123, parts: { "A" => 456 } }) + expect(BankTransactionReference.parse('x,FS123A456')).to match({ group: 123, parts: { 'A' => 456 } }) end it 'returns correct value for FS123A456 with minus-separated prefix' do - expect(BankTransactionReference.parse('x-FS123A456')).to match({ group: 123, parts: { "A" => 456 } }) + expect(BankTransactionReference.parse('x-FS123A456')).to match({ group: 123, parts: { 'A' => 456 } }) end it 'returns correct value for FS123A456 with semicolon-separated prefix' do - expect(BankTransactionReference.parse('x;FS123A456')).to match({ group: 123, parts: { "A" => 456 } }) + expect(BankTransactionReference.parse('x;FS123A456')).to match({ group: 123, parts: { 'A' => 456 } }) end it 'returns correct value for FS123A456 with space-separated prefix' do - expect(BankTransactionReference.parse('x FS123A456')).to match({ group: 123, parts: { "A" => 456 } }) + expect(BankTransactionReference.parse('x FS123A456')).to match({ group: 123, parts: { 'A' => 456 } }) end it 'returns correct value for FS234A567 with comma-separated suffix' do - expect(BankTransactionReference.parse('FS234A567,x')).to match({ group: 234, parts: { "A" => 567 } }) + expect(BankTransactionReference.parse('FS234A567,x')).to match({ group: 234, parts: { 'A' => 567 } }) end it 'returns correct value for FS234A567 with minus-separated suffix' do - expect(BankTransactionReference.parse('FS234A567-x')).to match({ group: 234, parts: { "A" => 567 } }) + expect(BankTransactionReference.parse('FS234A567-x')).to match({ group: 234, parts: { 'A' => 567 } }) end it 'returns correct value for FS234A567 with space-separated suffix' do - expect(BankTransactionReference.parse('FS234A567 x')).to match({ group: 234, parts: { "A" => 567 } }) + expect(BankTransactionReference.parse('FS234A567 x')).to match({ group: 234, parts: { 'A' => 567 } }) end it 'returns correct value for FS234A567 with semicolon-separated suffix' do - expect(BankTransactionReference.parse('FS234A567;x')).to match({ group: 234, parts: { "A" => 567 } }) + expect(BankTransactionReference.parse('FS234A567;x')).to match({ group: 234, parts: { 'A' => 567 } }) end it 'returns correct value for FS234A567 with minus-separated suffix' do - expect(BankTransactionReference.parse('FS234A567-x')).to match({ group: 234, parts: { "A" => 567 } }) + expect(BankTransactionReference.parse('FS234A567-x')).to match({ group: 234, parts: { 'A' => 567 } }) end it 'returns correct value for FS34.56A67.89 with prefix and suffix' do - expect(BankTransactionReference.parse('prefix FS34.56A67.89, suffix')).to match({ group: 34, user: 56, parts: { "A" => 67.89 } }) + expect(BankTransactionReference.parse('prefix FS34.56A67.89, suffix')).to match({ group: 34, user: 56, + parts: { 'A' => 67.89 } }) end end diff --git a/spec/lib/foodsoft_config_spec.rb b/spec/lib/foodsoft_config_spec.rb index be058f38..a6aa0539 100644 --- a/spec/lib/foodsoft_config_spec.rb +++ b/spec/lib/foodsoft_config_spec.rb @@ -9,7 +9,7 @@ end it 'returns an empty default value' do - expect(FoodsoftConfig[:protected][:LIUhniuyGNKUQTWfbiOQIWYexngo78hqexul]).to be nil + expect(FoodsoftConfig[:protected][:LIUhniuyGNKUQTWfbiOQIWYexngo78hqexul]).to be_nil end it 'returns a configuration value' do diff --git a/spec/lib/foodsoft_mail_receiver_spec.rb b/spec/lib/foodsoft_mail_receiver_spec.rb index 59eab47b..47ddb57d 100644 --- a/spec/lib/foodsoft_mail_receiver_spec.rb +++ b/spec/lib/foodsoft_mail_receiver_spec.rb @@ -6,72 +6,64 @@ @server.start end + # TODO: Reanable this test. + # It raised "Mysql2::Error: Lock wait timeout exceeded" at time of writing. + # it 'accepts bounce mails via SMTP' do + # MailDeliveryStatus.delete_all + # + # Net::SMTP.start(@server.host, @server.port) do |smtp| + # address = "#{FoodsoftConfig[:default_scope]}.bounce+user=example.com" + # smtp.send_message 'report', 'from@example.com', address + # end + # + # mds = MailDeliveryStatus.last + # expect(mds.email).to eq 'user@example.com' + # expect(mds.attachment_mime).to eq 'message/rfc822' + # expect(mds.attachment_data).to include 'report' + # end + + after :all do + @server.shutdown + end + it 'does not accept empty addresses' do - begin - FoodsoftMailReceiver.received('', 'body') - rescue => error - expect(error.to_s).to include 'missing' - end + FoodsoftMailReceiver.received('', 'body') + rescue StandardError => e + expect(e.to_s).to include 'missing' end it 'does not accept invalid addresses' do - begin - FoodsoftMailReceiver.received('invalid', 'body') - rescue => error - expect(error.to_s).to include 'has an invalid format' - end + FoodsoftMailReceiver.received('invalid', 'body') + rescue StandardError => e + expect(e.to_s).to include 'has an invalid format' end it 'does not accept invalid scope in address' do - begin - FoodsoftMailReceiver.received('invalid.invalid', 'body') - rescue => error - expect(error.to_s).to include 'could not be found' - end + FoodsoftMailReceiver.received('invalid.invalid', 'body') + rescue StandardError => e + expect(e.to_s).to include 'could not be found' end it 'does not accept address without handler' do - begin - address = "#{FoodsoftConfig[:default_scope]}.invalid" - FoodsoftMailReceiver.received(address, 'body') - rescue => error - expect(error.to_s).to include 'invalid format for recipient' - end + address = "#{FoodsoftConfig[:default_scope]}.invalid" + FoodsoftMailReceiver.received(address, 'body') + rescue StandardError => e + expect(e.to_s).to include 'invalid format for recipient' end it 'does not accept invalid addresses via SMTP' do - expect { + expect do Net::SMTP.start(@server.hosts.first, @server.ports.first) do |smtp| smtp.send_message 'body', 'from@example.com', 'invalid' end - }.to raise_error(Net::SMTPFatalError) + end.to raise_error(Net::SMTPFatalError) end it 'does not accept invalid addresses via SMTP' do - expect { + expect do Net::SMTP.start(@server.hosts.first, @server.ports.first) do |smtp| smtp.send_message 'body', 'from@example.com', 'invalid' end - }.to raise_error(Net::SMTPFatalError) - end - - # TODO: Reanable this test. - # It raised "Mysql2::Error: Lock wait timeout exceeded" at time of writing. - # it 'accepts bounce mails via SMTP' do - # MailDeliveryStatus.delete_all - # - # Net::SMTP.start(@server.host, @server.port) do |smtp| - # address = "#{FoodsoftConfig[:default_scope]}.bounce+user=example.com" - # smtp.send_message 'report', 'from@example.com', address - # end - # - # mds = MailDeliveryStatus.last - # expect(mds.email).to eq 'user@example.com' - # expect(mds.attachment_mime).to eq 'message/rfc822' - # expect(mds.attachment_data).to include 'report' - # end - - after :all do - @server.shutdown + end.to raise_error(Net::SMTPFatalError) end end diff --git a/spec/lib/token_verifier_spec.rb b/spec/lib/token_verifier_spec.rb index a5c81d4f..3097e888 100644 --- a/spec/lib/token_verifier_spec.rb +++ b/spec/lib/token_verifier_spec.rb @@ -1,17 +1,17 @@ require_relative '../spec_helper' describe TokenVerifier do - let (:prefix) { 'xyz' } - let (:v) { TokenVerifier.new(prefix) } - let (:msg) { v.generate } + let(:prefix) { 'xyz' } + let(:v) { TokenVerifier.new(prefix) } + let(:msg) { v.generate } it 'validates' do - expect { v.verify(msg) }.to_not raise_error + expect { v.verify(msg) }.not_to raise_error end it 'validates when recreated' do v2 = TokenVerifier.new(prefix) - expect { v2.verify(msg) }.to_not raise_error + expect { v2.verify(msg) }.not_to raise_error end it 'does not validate with a different prefix' do @@ -32,7 +32,9 @@ end it 'does not validate a random string' do - expect { v.verify(Faker::Lorem.characters(number: 100)) }.to raise_error(ActiveSupport::MessageVerifier::InvalidSignature) + expect do + v.verify(Faker::Lorem.characters(number: 100)) + end.to raise_error(ActiveSupport::MessageVerifier::InvalidSignature) end it 'returns the message' do diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index 40201570..51bd6140 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -1,12 +1,16 @@ require_relative '../spec_helper' describe Article do - let(:supplier) { create :supplier } - let(:article) { create :article, supplier: supplier } + let(:supplier) { create(:supplier) } + let(:article) { create(:article, supplier: supplier) } + let(:user) { create(:user, groups: [create(:ordergroup)]) } it 'has a unique name' do - article2 = FactoryBot.build :article, supplier: supplier, name: article.name - expect(article2).to be_invalid + article_version_copy = article.latest_article_version.dup + article2 = FactoryBot.build(:article, name: article.name, supplier_id: article.supplier_id, + latest_article_version: article_version_copy) + article2.latest_article_version.article = article2 + expect(article2.latest_article_version).to be_invalid end it 'computes the gross price correctly' do @@ -31,13 +35,27 @@ expect(supplier.deleted?).to be true end - it 'keeps a price history' do - expect(article.article_prices.map(&:price)).to eq([article.price]) + it "doesn't keep an article history for articles not referenced by closed orders" do + expect(article.article_versions.map(&:price)).to eq([article.price]) oldprice = article.price sleep 1 # so that the new price really has a later creation time article.price += 1 article.save! - expect(article.article_prices.reload.map(&:price)).to eq([article.price, oldprice]) + expect(article.article_versions.reload.map(&:price)).to eq([article.price]) + end + + it 'keeps an article history for articles referenced by closed orders' do + order = create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close) + order.close!(user) + article = order.order_articles.first.article_version.article + + expect(article.article_versions.map(&:price)).to eq([article.price]) + oldprice = article.price + + sleep 1 # so that the new price really has a later creation time + article.price += 1 + article.save! + expect(article.article_versions.reload.map(&:price)).to eq([article.price, oldprice]) end it 'is not in an open order by default' do @@ -45,7 +63,7 @@ end it 'is knows its open order' do - order = create :order, supplier: supplier, article_ids: [article.id] + order = create(:order, supplier: supplier, article_ids: [article.id]) expect(article.in_open_order).to eq(order) end @@ -53,26 +71,35 @@ expect(article.shared_article).to be_nil end - describe 'connected to a shared database', :type => :feature do - let(:shared_article) { create :shared_article } - let(:supplier) { create :supplier, shared_supplier_id: shared_article.supplier_id } - let(:article) { create :article, supplier: supplier, order_number: shared_article.order_number } + describe 'connected to a shared database', type: :feature do + let(:shared_article) { create(:shared_article) } + let(:supplier) { create(:supplier, shared_supplier_id: shared_article.supplier_id) } + let(:article) do + article = create(:article, supplier: supplier) + article_version = article.latest_article_version + article_version.order_number = shared_article.number + article_version.save + article + end it 'can be found in the shared database' do - expect(article.shared_article).to_not be_nil + expect(article.shared_article).not_to be_nil end it 'can find updates' do changed = article.shared_article_changed? - expect(changed).to_not be_falsey + expect(changed).not_to be_falsey expect(changed.length).to be > 1 end it 'can be synchronised' do - # TODO move article sync from supplier to article + # TODO: move article sync from supplier to article article # need to reference for it to exist when syncing - updated_article = supplier.sync_all[0].select { |s| s[0].id == article.id }.first[0] - article.update_attributes updated_article.attributes.reject { |k, v| k == 'id' or k == 'type' } + sync_data = supplier.sync_all + updated_article = sync_data[0].select { |s| s[0].id == article.id }.first[0] + version_attributes = updated_article.latest_article_version.attributes.reject { |k, _v| k == 'id' } + version_attributes[:id] = article.latest_article_version.id + article.update_attributes(latest_article_version_attributes: version_attributes) expect(article.name).to eq(shared_article.name) # now synchronising shouldn't change anything anymore expect(article.shared_article_changed?).to be_falsey @@ -80,7 +107,7 @@ it 'does not need to synchronise an imported article' do article = shared_article.build_new_article(supplier) - article.article_category = create :article_category + article.latest_article_version.article_category = create :article_category expect(article.shared_article_changed?).to be_falsey end @@ -89,20 +116,35 @@ shared_article.unit_quantity = 1 shared_article.save! article = shared_article.build_new_article(supplier) - article.article_category = create :article_category - article.unit = '200g' + article_version = article.latest_article_version + + # remove version before save - required to make validation pass (can't validate article_version without existing article_id): + article.article_versions = [] + article.latest_article_version = nil + article.shared_updated_on -= 1 # to make update do something article.save! - # TODO get sync functionality in article - updated_article = supplier.sync_all[0].select { |s| s[0].id == article.id }.first[0] - article.update_attributes! updated_article.attributes.reject { |k, v| k == 'id' or k == 'type' } + + article_version.article_category = create :article_category + article_version.unit = '200g' + article.article_versions << article_version + article.reload + # TODO: get sync functionality in article + sync_data = supplier.sync_all + updated_article = sync_data[0].select { |s| s[0].id == article.id }.first[0] + version_attributes = updated_article.latest_article_version.attributes.reject { |k, _v| k == 'id' } + version_attributes[:article_unit_ratios_attributes] = + updated_article.latest_article_version.article_unit_ratios.map(&:attributes) + version_attributes[:id] = article.latest_article_version.id + article.update_attributes(latest_article_version_attributes: version_attributes) + expect(article.unit).to eq '200g' expect(article.unit_quantity).to eq 5 expect(article.price).to be_within(0.005).of(shared_article.price / 5) end it 'does not synchronise when it has no order number' do - article.update_attributes :order_number => nil + article.update_attributes order_number: nil expect(supplier.sync_all).to eq [[], [], []] end end diff --git a/spec/models/bank_transaction_spec.rb b/spec/models/bank_transaction_spec.rb index 984d39ae..14172676 100644 --- a/spec/models/bank_transaction_spec.rb +++ b/spec/models/bank_transaction_spec.rb @@ -1,24 +1,32 @@ require_relative '../spec_helper' describe BankTransaction do - let(:bank_account) { create :bank_account } - let(:ordergroup) { create :ordergroup } - let(:supplier) { create :supplier, iban: Faker::Bank.iban } - let!(:user) { create :user, groups: [ordergroup] } - let!(:ftt_a) { create :financial_transaction_type, name_short: 'A' } - let!(:ftt_b) { create :financial_transaction_type, name_short: 'B' } + let(:bank_account) { create(:bank_account) } + let(:ordergroup) { create(:ordergroup) } + let(:supplier) { create(:supplier, iban: Faker::Bank.iban) } + let!(:user) { create(:user, groups: [ordergroup]) } + let!(:ftt_a) { create(:financial_transaction_type, name_short: 'A') } + let!(:ftt_b) { create(:financial_transaction_type, name_short: 'B') } describe 'supplier' do - let!(:invoice1) { create :invoice, supplier: supplier, number: '11', amount: 10 } - let!(:invoice2) { create :invoice, supplier: supplier, number: '22', amount: 20 } - let!(:invoice3) { create :invoice, supplier: supplier, number: '33', amount: 30 } - let!(:invoice4) { create :invoice, supplier: supplier, number: '44', amount: 40 } - let!(:invoice5) { create :invoice, supplier: supplier, number: '55', amount: 50 } - - let!(:bank_transaction1) { create :bank_transaction, bank_account: bank_account, iban: supplier.iban, reference: '11', amount: 10 } - let!(:bank_transaction2) { create :bank_transaction, bank_account: bank_account, iban: supplier.iban, reference: '22', amount: -20 } - let!(:bank_transaction3) { create :bank_transaction, bank_account: bank_account, iban: supplier.iban, reference: '33,44', amount: -70 } - let!(:bank_transaction4) { create :bank_transaction, bank_account: bank_account, iban: supplier.iban, text: '55', amount: -50 } + let!(:invoice1) { create(:invoice, supplier: supplier, number: '11', amount: 10) } + let!(:invoice2) { create(:invoice, supplier: supplier, number: '22', amount: 20) } + let!(:invoice3) { create(:invoice, supplier: supplier, number: '33', amount: 30) } + let!(:invoice4) { create(:invoice, supplier: supplier, number: '44', amount: 40) } + let!(:invoice5) { create(:invoice, supplier: supplier, number: '55', amount: 50) } + + let!(:bank_transaction1) do + create(:bank_transaction, bank_account: bank_account, iban: supplier.iban, reference: '11', amount: 10) + end + let!(:bank_transaction2) do + create(:bank_transaction, bank_account: bank_account, iban: supplier.iban, reference: '22', amount: -20) + end + let!(:bank_transaction3) do + create(:bank_transaction, bank_account: bank_account, iban: supplier.iban, reference: '33,44', amount: -70) + end + let!(:bank_transaction4) do + create(:bank_transaction, bank_account: bank_account, iban: supplier.iban, text: '55', amount: -50) + end it 'ignores invoices with invalid amount' do expect(bank_transaction1.assign_to_invoice).to be false @@ -49,17 +57,29 @@ end describe 'ordergroup' do - let!(:bank_transaction1) { create :bank_transaction, bank_account: bank_account, reference: "invalid", amount: 10 } - let!(:bank_transaction2) { create :bank_transaction, bank_account: bank_account, reference: "FS99A10", amount: 10 } - let!(:bank_transaction3) { create :bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}.99A10", amount: 10 } - let!(:bank_transaction4) { create :bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}A10", amount: 99 } - let!(:bank_transaction5) { create :bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}A10", amount: 10 } - let!(:bank_transaction6) { create :bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}A10B20", amount: 30 } - let!(:bank_transaction7) { create :bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}.#{user.id}A10", amount: 10 } - let!(:bank_transaction8) { create :bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}X10", amount: 10 } + let!(:bank_transaction1) { create(:bank_transaction, bank_account: bank_account, reference: 'invalid', amount: 10) } + let!(:bank_transaction2) { create(:bank_transaction, bank_account: bank_account, reference: 'FS99A10', amount: 10) } + let!(:bank_transaction3) do + create(:bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}.99A10", amount: 10) + end + let!(:bank_transaction4) do + create(:bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}A10", amount: 99) + end + let!(:bank_transaction5) do + create(:bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}A10", amount: 10) + end + let!(:bank_transaction6) do + create(:bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}A10B20", amount: 30) + end + let!(:bank_transaction7) do + create(:bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}.#{user.id}A10", amount: 10) + end + let!(:bank_transaction8) do + create(:bank_transaction, bank_account: bank_account, reference: "FS#{ordergroup.id}X10", amount: 10) + end it 'ignores transaction with invalid reference' do - expect(bank_transaction1.assign_to_ordergroup).to be nil + expect(bank_transaction1.assign_to_ordergroup).to be_nil end it 'ignores transaction with invalid ordergroup' do diff --git a/spec/models/group_order_article_spec.rb b/spec/models/group_order_article_spec.rb index ddb9158a..b777eb89 100644 --- a/spec/models/group_order_article_spec.rb +++ b/spec/models/group_order_article_spec.rb @@ -1,20 +1,31 @@ require_relative '../spec_helper' describe GroupOrderArticle do - let(:user) { create :user, groups: [create(:ordergroup)] } + let(:user) { create(:user, groups: [create(:ordergroup)]) } let(:order) { create(:order) } - let(:go) { create :group_order, order: order, ordergroup: user.ordergroup } - let(:goa) { create :group_order_article, group_order: go, order_article: order.order_articles.first } + let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } + let(:goa) { create(:group_order_article, group_order: go, order_article: order.order_articles.first) } - it 'has zero quantity by default' do expect(goa.quantity).to eq(0) end - it 'has zero tolerance by default' do expect(goa.tolerance).to eq(0) end - it 'has zero result by default' do expect(goa.result).to eq(0) end - it 'has zero total price by default' do expect(goa.total_price).to eq(0) end + it 'has zero quantity by default' do + expect(goa.quantity).to eq(0) + end + + it 'has zero tolerance by default' do + expect(goa.tolerance).to eq(0) + end + + it 'has zero result by default' do + expect(goa.result).to eq(0) + end + + it 'has zero total price by default' do + expect(goa.total_price).to eq(0) + end describe do - let(:article) { create :article, supplier: order.supplier, unit_quantity: 1 } - let(:oa) { order.order_articles.create(:article => article) } - let(:goa) { create :group_order_article, group_order: go, order_article: oa } + let(:article) { create(:article, supplier: order.supplier, unit_quantity: 1) } + let(:oa) { order.order_articles.create(article: article) } + let(:goa) { create(:group_order_article, group_order: go, order_article: oa) } it 'can be ordered by piece' do goa.update_quantities(1, 0) @@ -23,7 +34,8 @@ end it 'can be ordered in larger amounts' do - quantity, tolerance = rand(13..99), rand(0..99) + quantity = rand(13..99) + tolerance = rand(0..99) goa.update_quantities(quantity, tolerance) expect(goa.quantity).to eq(quantity) expect(goa.tolerance).to eq(tolerance) @@ -32,7 +44,7 @@ it 'has a proper total price' do quantity = rand(1..99) goa.update_quantities(quantity, 0) - expect(goa.total_price).to eq(quantity * goa.order_article.price.fc_price) + expect(goa.total_price).to eq(quantity * goa.order_article.article_version.fc_price) end it 'can unorder a product' do @@ -43,10 +55,10 @@ end describe 'distribution strategy' do - let(:article) { create :article, supplier: order.supplier, unit_quantity: 1 } - let(:oa) { order.order_articles.create(:article => article) } - let(:goa) { create :group_order_article, group_order: go, order_article: oa } - let!(:goaq) { create :group_order_article_quantity, group_order_article: goa, quantity: 4 } + let(:article) { create(:article, supplier: order.supplier, unit_quantity: 1) } + let(:oa) { order.order_articles.create(article: article) } + let(:goa) { create(:group_order_article, group_order: go, order_article: oa) } + let!(:goaq) { create(:group_order_article_quantity, group_order_article: goa, quantity: 4) } it 'can calculate the result for the distribution strategy "first order first serve"' do res = goa.calculate_result(2) diff --git a/spec/models/group_order_spec.rb b/spec/models/group_order_spec.rb index a2b8a2c5..648fa100 100644 --- a/spec/models/group_order_spec.rb +++ b/spec/models/group_order_spec.rb @@ -1,8 +1,8 @@ require_relative '../spec_helper' describe GroupOrder do - let(:user) { create :user, groups: [create(:ordergroup)] } - let(:order) { create :order } + let(:user) { create(:user, groups: [create(:ordergroup)]) } + let(:order) { create(:order) } # the following two tests are currently disabled - https://github.com/foodcoops/foodsoft/issues/158 @@ -15,7 +15,7 @@ # end describe do - let(:go) { create :group_order, order: order, ordergroup: user.ordergroup } + let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } it 'has zero price initially' do expect(go.price).to eq(0) diff --git a/spec/models/order_article_spec.rb b/spec/models/order_article_spec.rb index d124678f..acade0ee 100644 --- a/spec/models/order_article_spec.rb +++ b/spec/models/order_article_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' describe OrderArticle do - let(:order) { create :order, article_count: 1 } + let(:order) { create(:order, article_count: 1) } let(:oa) { order.order_articles.first } it 'is not ordered by default' do expect(OrderArticle.ordered.count).to eq 0 end - [:units_to_order, :units_billed, :units_received].each do |units| + %i[units_to_order units_billed units_received].each do |units| it "is ordered when there are #{units.to_s.gsub '_', ' '}" do oa.update_attribute units, rand(1..99) expect(OrderArticle.ordered.count).to eq 1 @@ -46,15 +46,15 @@ end describe 'redistribution' do - let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } - let(:article) { create :article, unit_quantity: 3 } - let(:order) { create :order, article_ids: [article.id] } - let(:go1) { create :group_order, order: order } - let(:go2) { create :group_order, order: order } - let(:go3) { create :group_order, order: order } - let(:goa1) { create :group_order_article, group_order: go1, order_article: oa } - let(:goa2) { create :group_order_article, group_order: go2, order_article: oa } - let(:goa3) { create :group_order_article, group_order: go3, order_article: oa } + let(:admin) { create(:user, groups: [create(:workgroup, role_finance: true)]) } + let(:article) { create(:article, unit_quantity: 3) } + let(:order) { create(:order, article_ids: [article.id]) } + let(:go1) { create(:group_order, order: order) } + let(:go2) { create(:group_order, order: order) } + let(:go3) { create(:group_order, order: order) } + let(:goa1) { create(:group_order_article, group_order: go1, order_article: oa) } + let(:goa2) { create(:group_order_article, group_order: go2, order_article: oa) } + let(:goa3) { create(:group_order_article, group_order: go3, order_article: oa) } # set quantities of group_order_articles def set_quantities(q1, q2, q3) @@ -73,27 +73,27 @@ def goa_reload it 'has expected units_to_order' do set_quantities [3, 2], [1, 3], [1, 0] - expect(oa.units * oa.article.unit_quantity).to eq 6 + expect(oa.units * oa.article_version.unit_quantity).to eq 6 expect([goa1, goa2, goa3].map(&:result)).to eq [4, 1, 1] end it 'does nothing when nothing has changed' do set_quantities [3, 2], [1, 3], [1, 0] - expect(oa.redistribute 6, [:tolerance, nil]).to eq [1, 0] + expect(oa.redistribute(6, [:tolerance, nil])).to eq [1, 0] goa_reload expect([goa1, goa2, goa3].map(&:result).map(&:to_i)).to eq [4, 1, 1] end it 'works when there is nothing to distribute' do set_quantities [3, 2], [1, 3], [1, 0] - expect(oa.redistribute 0, [:tolerance, nil]).to eq [0, 0] + expect(oa.redistribute(0, [:tolerance, nil])).to eq [0, 0] goa_reload expect([goa1, goa2, goa3].map(&:result)).to eq [0, 0, 0] end it 'works when quantity needs to be reduced' do set_quantities [3, 2], [1, 3], [1, 0] - expect(oa.redistribute 4, [:tolerance, nil]).to eq [0, 0] + expect(oa.redistribute(4, [:tolerance, nil])).to eq [0, 0] goa_reload expect([goa1, goa2, goa3].map(&:result)).to eq [3, 1, 0] end @@ -101,28 +101,28 @@ def goa_reload it 'works when quantity is increased within quantity' do set_quantities [3, 0], [2, 0], [2, 0] expect([goa1, goa2, goa3].map(&:result)).to eq [3, 2, 1] - expect(oa.redistribute 7, [:tolerance, nil]).to eq [0, 0] + expect(oa.redistribute(7, [:tolerance, nil])).to eq [0, 0] goa_reload expect([goa1, goa2, goa3].map(&:result).map(&:to_i)).to eq [3, 2, 2] end it 'works when there is just one for the first' do set_quantities [3, 2], [1, 3], [1, 0] - expect(oa.redistribute 1, [:tolerance, nil]).to eq [0, 0] + expect(oa.redistribute(1, [:tolerance, nil])).to eq [0, 0] goa_reload expect([goa1, goa2, goa3].map(&:result)).to eq [1, 0, 0] end it 'works when there is tolerance and left-over' do set_quantities [3, 2], [1, 1], [1, 0] - expect(oa.redistribute 10, [:tolerance, nil]).to eq [3, 2] + expect(oa.redistribute(10, [:tolerance, nil])).to eq [3, 2] goa_reload expect([goa1, goa2, goa3].map(&:result)).to eq [5, 2, 1] end it 'works when redistributing without tolerance' do set_quantities [3, 2], [1, 3], [1, 0] - expect(oa.redistribute 8, [nil]).to eq [3] + expect(oa.redistribute(8, [nil])).to eq [3] goa_reload expect([goa1, goa2, goa3].map(&:result)).to eq [3, 1, 1] end @@ -130,17 +130,19 @@ def goa_reload describe 'boxfill' do before { FoodsoftConfig[:use_boxfill] = true } - let(:article) { create :article, unit_quantity: 6 } - let(:order) { create :order, article_ids: [article.id], starts: 1.week.ago } + + let(:article) { create(:article, unit_quantity: 6) } + let(:order) { create(:order, article_ids: [article.id], starts: 1.week.ago) } let(:oa) { order.order_articles.first } - let(:go) { create :group_order, order: order } - let(:goa) { create :group_order_article, group_order: go, order_article: oa } + let(:go) { create(:group_order, order: order) } + let(:goa) { create(:group_order_article, group_order: go, order_article: oa) } - shared_examples "boxfill" do |success, q| + shared_examples 'boxfill' do |success, q| # initial situation before do - goa.update_quantities *q.keys[0] - oa.update_results!; oa.reload + goa.update_quantities(*q.keys[0]) + oa.update_results! + oa.reload end # check starting condition @@ -149,11 +151,11 @@ def goa_reload end # actual test - it (success ? 'succeeds' : 'fails') do + it(success ? 'succeeds' : 'fails') do order.update_attributes(boxfill: boxfill_from) r = proc { - goa.update_quantities *q.values[0] + goa.update_quantities(*q.values[0]) oa.update_results! } if success @@ -169,33 +171,41 @@ def goa_reload context 'before the date' do let(:boxfill_from) { 1.hour.from_now } + context 'decreasing the missing units' do - include_examples "boxfill", true, [6, 0] => [5, 0], [6, 0, 0] => [5, 0, 1] + include_examples 'boxfill', true, [6, 0] => [5, 0], [6, 0, 0] => [5, 0, 1] end + context 'decreasing the tolerance' do - include_examples "boxfill", true, [1, 2] => [1, 1], [1, 2, 3] => [1, 1, 4] + include_examples 'boxfill', true, [1, 2] => [1, 1], [1, 2, 3] => [1, 1, 4] end end context 'after the date' do let(:boxfill_from) { 1.second.ago } + context 'changing nothing in particular' do - include_examples "boxfill", true, [4, 1] => [4, 1], [4, 1, 1] => [4, 1, 1] + include_examples 'boxfill', true, [4, 1] => [4, 1], [4, 1, 1] => [4, 1, 1] end + context 'increasing missing units' do - include_examples "boxfill", false, [3, 0] => [2, 0], [3, 0, 3] => [3, 0, 3] + include_examples 'boxfill', false, [3, 0] => [2, 0], [3, 0, 3] => [3, 0, 3] end + context 'increasing tolerance' do - include_examples "boxfill", true, [2, 1] => [2, 2], [2, 1, 3] => [2, 2, 2] + include_examples 'boxfill', true, [2, 1] => [2, 2], [2, 1, 3] => [2, 2, 2] end + context 'decreasing quantity to fix missing units' do - include_examples "boxfill", true, [7, 0] => [6, 0], [7, 0, 5] => [6, 0, 0] + include_examples 'boxfill', true, [7, 0] => [6, 0], [7, 0, 5] => [6, 0, 0] end + context 'decreasing quantity keeping missing units equal' do - include_examples "boxfill", false, [7, 0] => [1, 0], [7, 0, 5] => [7, 0, 5] + include_examples 'boxfill', false, [7, 0] => [1, 0], [7, 0, 5] => [7, 0, 5] end + context 'moving tolerance to quantity' do - include_examples "boxfill", true, [4, 2] => [6, 0], [4, 2, 0] => [6, 0, 0] + include_examples 'boxfill', true, [4, 2] => [6, 0], [4, 2, 0] => [6, 0, 0] end # @todo enable test when tolerance doesn't count in missing_units # context 'decreasing tolerance' do diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 10a91a7e..18a478b4 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -1,14 +1,14 @@ require_relative '../spec_helper' describe Order do - let!(:ftt) { create :financial_transaction_type } - let(:user) { create :user, groups: [create(:ordergroup)] } + let!(:ftt) { create(:financial_transaction_type) } + let(:user) { create(:user, groups: [create(:ordergroup)]) } it 'automaticly finishes ended' do - create :order, created_by: user, starts: Date.yesterday, ends: 1.hour.from_now - create :order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago - create :order, created_by: user, starts: Date.yesterday, ends: 1.hour.from_now, end_action: :auto_close - order = create :order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close + create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.from_now) + create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago) + create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.from_now, end_action: :auto_close) + order = create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close) Order.finish_ended! order.reload @@ -19,51 +19,51 @@ end describe 'state scopes and boolean getters' do - let!(:open_order) { create :order, state: 'open' } - let!(:finished_order) { create :order, state: 'finished' } - let!(:received_order) { create :order, state: 'received' } - let!(:closed_order) { create :order, state: 'closed' } + let!(:open_order) { create(:order, state: 'open') } + let!(:finished_order) { create(:order, state: 'finished') } + let!(:received_order) { create(:order, state: 'received') } + let!(:closed_order) { create(:order, state: 'closed') } - it 'should retrieve open orders in the "open" scope' do + it 'retrieves open orders in the "open" scope' do expect(Order.open.count).to eq(1) expect(Order.open.where(id: open_order.id)).to exist end - it 'should retrieve finished, received and closed orders in the "finished" scope' do + it 'retrieves finished, received and closed orders in the "finished" scope' do expect(Order.finished.count).to eq(3) expect(Order.finished.where(id: finished_order.id)).to exist expect(Order.finished.where(id: received_order.id)).to exist expect(Order.finished.where(id: closed_order.id)).to exist end - it 'should retrieve finished and received orders in the "finished_not_closed" scope' do + it 'retrieves finished and received orders in the "finished_not_closed" scope' do expect(Order.finished_not_closed.count).to eq(2) expect(Order.finished_not_closed.where(id: finished_order.id)).to exist expect(Order.finished_not_closed.where(id: received_order.id)).to exist end - it 'should return valid boolean states for open orders' do + it 'returns valid boolean states for open orders' do expect(open_order.open?).to be(true) expect(open_order.finished?).to be(false) expect(open_order.received?).to be(false) expect(open_order.closed?).to be(false) end - it 'should return valid boolean states for finished orders' do + it 'returns valid boolean states for finished orders' do expect(finished_order.open?).to be(false) expect(finished_order.finished?).to be(true) expect(finished_order.received?).to be(false) expect(finished_order.closed?).to be(false) end - it 'should return valid boolean states for received orders' do + it 'returns valid boolean states for received orders' do expect(received_order.open?).to be(false) expect(received_order.finished?).to be(true) expect(received_order.received?).to be(true) expect(received_order.closed?).to be(false) end - it 'should return valid boolean states for closed orders' do + it 'returns valid boolean states for closed orders' do expect(closed_order.open?).to be(false) expect(closed_order.finished?).to be(false) expect(closed_order.received?).to be(false) @@ -72,8 +72,9 @@ end it 'sends mail if min_order_quantity has been reached' do - create :user, groups: [create(:ordergroup)] - create :order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, end_action: :auto_close_and_send_min_quantity + create(:user, groups: [create(:ordergroup)]) + create(:order, created_by: user, starts: Date.yesterday, ends: 1.hour.ago, + end_action: :auto_close_and_send_min_quantity) Order.finish_ended! expect(ActionMailer::Base.deliveries.count).to eq 1 @@ -84,7 +85,7 @@ end it 'needs order articles' do - supplier = create :supplier, article_count: 0 + supplier = create(:supplier, article_count: 0) expect(build(:order, supplier: supplier)).to be_invalid end @@ -93,35 +94,44 @@ end describe 'with articles' do - let(:order) { create :order } + let(:order) { create(:order) } - it 'is open by default' do expect(order).to be_open end - it 'is not finished by default' do expect(order).to_not be_finished end - it 'is not closed by default' do expect(order).to_not be_closed end + it 'is open by default' do + expect(order).to be_open + end + + it 'is not finished by default' do + expect(order).not_to be_finished + end + + it 'is not closed by default' do + expect(order).not_to be_closed + end it 'has valid order articles' do order.order_articles.each { |oa| expect(oa).to be_valid } end it 'can be finished' do - # TODO randomise user + # TODO: randomise user order.finish!(user) - expect(order).to_not be_open + expect(order).not_to be_open expect(order).to be_finished - expect(order).to_not be_closed + expect(order).not_to be_closed end it 'can be closed' do - # TODO randomise user + # TODO: randomise user order.finish!(user) order.close!(user) - expect(order).to_not be_open + expect(order).not_to be_open expect(order).to be_closed end end describe 'with a default end date' do - let(:order) { create :order } + let(:order) { create(:order) } + before do FoodsoftConfig[:order_schedule] = { ends: { recurr: 'FREQ=WEEKLY;BYDAY=MO', time: '9:00' } } order.init_dates @@ -137,10 +147,10 @@ end describe 'mapped to GroupOrders' do - let!(:user) { create :user, groups: [create(:ordergroup)] } - let!(:order) { create :order } - let!(:order2) { create :order } - let!(:go) { create :group_order, order: order, ordergroup: user.ordergroup } + let!(:user) { create(:user, groups: [create(:ordergroup)]) } + let!(:order) { create(:order) } + let!(:order2) { create(:order) } + let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } it 'to map a user\'s GroupOrders to a list of Orders' do orders = Order.ordergroup_group_orders_map(user.ordergroup) @@ -155,10 +165,10 @@ describe 'balancing charges correct amounts' do let!(:transport) { rand(0.1..26.0).round(2) } - let!(:order) { create :order, article_count: 1 } + let!(:order) { create(:order, article_count: 1) } let!(:oa) { order.order_articles.first } - let!(:go) { create :group_order, order: order, transport: transport } - let!(:goa) { create :group_order_article, group_order: go, order_article: oa, quantity: 1 } + let!(:go) { create(:group_order, order: order, transport: transport) } + let!(:goa) { create(:group_order_article, group_order: go, order_article: oa, quantity: 1) } before do goa.update_quantities(1, 0) diff --git a/spec/models/ordergroup_spec.rb b/spec/models/ordergroup_spec.rb index 6ac58fd5..565ad32f 100644 --- a/spec/models/ordergroup_spec.rb +++ b/spec/models/ordergroup_spec.rb @@ -1,12 +1,12 @@ require_relative '../spec_helper' describe Ordergroup do - let(:ftc1) { create :financial_transaction_class } - let(:ftc2) { create :financial_transaction_class } - let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 } - let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 } - let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 } - let(:user) { create :user, groups: [create(:ordergroup)] } + let(:ftc1) { create(:financial_transaction_class) } + let(:ftc2) { create(:financial_transaction_class) } + let(:ftt1) { create(:financial_transaction_type, financial_transaction_class: ftc1) } + let(:ftt2) { create(:financial_transaction_type, financial_transaction_class: ftc2) } + let(:ftt3) { create(:financial_transaction_type, financial_transaction_class: ftc2) } + let(:user) { create(:user, groups: [create(:ordergroup)]) } context 'with financial transactions' do before do diff --git a/spec/models/supplier_spec.rb b/spec/models/supplier_spec.rb index 72e870ed..f92e140c 100644 --- a/spec/models/supplier_spec.rb +++ b/spec/models/supplier_spec.rb @@ -1,41 +1,61 @@ require_relative '../spec_helper' describe Supplier do - let(:supplier) { create :supplier } + let(:supplier) { create(:supplier) } it 'has a unique name' do - supplier2 = build :supplier, name: supplier.name + supplier2 = build(:supplier, name: supplier.name) expect(supplier2).to be_invalid end it 'has valid articles' do - supplier = create :supplier, article_count: true + supplier = create(:supplier, article_count: true) supplier.articles.each { |a| expect(a).to be_valid } end context 'connected to a shared supplier' do let(:shared_sync_method) { nil } - let(:shared_supplier) { create :shared_supplier } - let(:supplier) { create :supplier, shared_supplier: shared_supplier, shared_sync_method: shared_sync_method } + let(:shared_supplier) { create(:shared_supplier) } + let(:supplier) { create(:supplier, shared_supplier: shared_supplier, shared_sync_method: shared_sync_method) } - let!(:synced_shared_article) { create :shared_article, shared_supplier: shared_supplier } - let!(:updated_shared_article) { create :shared_article, shared_supplier: shared_supplier } - let!(:new_shared_article) { create :shared_article, shared_supplier: shared_supplier } + let!(:synced_shared_article) { create(:shared_article, shared_supplier: shared_supplier) } + let!(:updated_shared_article) { create(:shared_article, shared_supplier: shared_supplier) } + let!(:new_shared_article) { create(:shared_article, shared_supplier: shared_supplier) } - let!(:removed_article) { create :article, supplier: supplier, order_number: '10001-ABC' } + let!(:removed_article) { create(:article, supplier: supplier, order_number: '10001-ABC') } let!(:updated_article) do updated_shared_article.build_new_article(supplier).tap do |article| - article.article_category = create :article_category - article.origin = "FubarX1" + article_version = article.latest_article_version + + # remove version before save - required to make validation pass (can't validate article_version without existing article_id): + article.article_versions = [] + article.latest_article_version = nil + article.shared_updated_on = 1.day.ago article.save! + + article_version.article_category = create :article_category + article_version.unit = '200g' + article_version.origin = 'FubarX1' + article.article_versions << article_version + article.reload end end let!(:synced_article) do synced_shared_article.build_new_article(supplier).tap do |article| - article.article_category = create :article_category + article_version = article.latest_article_version + + # remove version before save - required to make validation pass (can't validate article_version without existing article_id): + article.article_versions = [] + article.latest_article_version = nil + article.shared_updated_on = 1.day.ago article.save! + + article_version.article_category = create :article_category + article_version.unit = '200g' + article.article_versions << article_version + article.reload end end @@ -45,9 +65,11 @@ it 'returns the expected articles' do updated_article_pairs, outlisted_articles, new_articles = supplier.sync_all - expect(updated_article_pairs).to_not be_empty - expect(updated_article_pairs[0][0].id).to eq updated_article.id - expect(updated_article_pairs[0][1].keys).to include :origin + expect(updated_article_pairs).not_to be_empty + + index = updated_article_pairs.index { |pair| pair[0].id == updated_article.id } + expect(index).not_to be_nil + expect(updated_article_pairs[index][1].keys).to include :origin expect(outlisted_articles).to eq [removed_article] @@ -61,15 +83,15 @@ it 'returns the expected articles' do updated_article_pairs, outlisted_articles, new_articles = supplier.sync_all - expect(updated_article_pairs).to_not be_empty - expect(updated_article_pairs[0][0].id).to eq updated_article.id - expect(updated_article_pairs[0][1].keys).to include :origin + index = updated_article_pairs.index { |pair| pair[0].id == updated_article.id } + expect(index).not_to be_nil + expect(updated_article_pairs[index][1].keys).to include :origin expect(outlisted_articles).to eq [removed_article] - expect(new_articles).to_not be_empty + expect(new_articles).not_to be_empty expect(new_articles[0].order_number).to eq new_shared_article.number - expect(new_articles[0].availability?).to be true + expect(new_articles[0].availability).to be true end end @@ -79,15 +101,15 @@ it 'returns the expected articles' do updated_article_pairs, outlisted_articles, new_articles = supplier.sync_all - expect(updated_article_pairs).to_not be_empty - expect(updated_article_pairs[0][0].id).to eq updated_article.id - expect(updated_article_pairs[0][1].keys).to include :origin + index = updated_article_pairs.index { |pair| pair[0].id == updated_article.id } + expect(index).not_to be_nil + expect(updated_article_pairs[index][1].keys).to include :origin expect(outlisted_articles).to eq [removed_article] - expect(new_articles).to_not be_empty + expect(new_articles).not_to be_empty expect(new_articles[0].order_number).to eq new_shared_article.number - expect(new_articles[0].availability?).to be false + expect(new_articles[0].availability).to be false end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2415cae8..9eb5f7d9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,9 +2,9 @@ describe User do it 'is correctly created' do - user = create :user, + user = create(:user, nick: 'johnnydoe', first_name: 'Johnny', last_name: 'DoeBar', - email: 'johnnydoe@foodcoop.test', phone: '+1234567890' + email: 'johnnydoe@foodcoop.test', phone: '+1234567890') expect(user.nick).to eq('johnnydoe') expect(user.first_name).to eq('Johnny') expect(user.last_name).to eq('DoeBar') @@ -14,34 +14,54 @@ end describe 'does not have the role' do - let(:user) { create :user } - it 'admin' do expect(user.role_admin?).to be_falsey end - it 'finance' do expect(user.role_finance?).to be_falsey end - it 'article_meta' do expect(user.role_article_meta?).to be_falsey end - it 'suppliers' do expect(user.role_suppliers?).to be_falsey end - it 'orders' do expect(user.role_orders?).to be_falsey end + let(:user) { create(:user) } + + it 'admin' do + expect(user.role_admin?).to be_falsey + end + + it 'finance' do + expect(user.role_finance?).to be_falsey + end + + it 'article_meta' do + expect(user.role_article_meta?).to be_falsey + end + + it 'suppliers' do + expect(user.role_suppliers?).to be_falsey + end + + it 'orders' do + expect(user.role_orders?).to be_falsey + end end describe do - let(:user) { create :user, password: 'blahblahblah' } + let(:user) { create(:user, password: 'blahblahblah') } it 'can authenticate with correct password' do expect(User.authenticate(user.nick, 'blahblahblah')).to be_truthy end + it 'can not authenticate with incorrect password' do expect(User.authenticate(user.nick, 'foobar')).to be_nil end + it 'can not authenticate with nil nick' do expect(User.authenticate(nil, 'blahblahblah')).to be_nil end + it 'can not authenticate with nil password' do expect(User.authenticate(user.nick, nil)).to be_nil end + it 'can not set a password without matching confirmation' do user.password = 'abcdefghijkl' user.password_confirmation = 'foobaruvwxyz' expect(user).to be_invalid end + it 'can set a password with matching confirmation' do user.password = 'abcdefghijkl' user.password_confirmation = 'abcdefghijkl' @@ -51,6 +71,7 @@ it 'has a unique nick' do expect(build(:user, nick: user.nick, email: "x-#{user.email}")).to be_invalid end + it 'has a unique email' do expect(build(:user, email: "#{user.email}")).to be_invalid end @@ -67,7 +88,10 @@ end describe 'admin' do - let(:user) { create :admin } - it 'default admin role' do expect(user.role_admin?).to be_truthy end + let(:user) { create(:admin) } + + it 'default admin role' do + expect(user.role_admin?).to be_truthy + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 88dea423..c1a3df21 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,8 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' -ENV["RAILS_ENV"] ||= 'test' -ENV["FOODSOFT_APP_CONFIG"] ||= 'spec/app_config.yml' # load special foodsoft config +ENV['RAILS_ENV'] ||= 'test' +ENV['FOODSOFT_APP_CONFIG'] ||= 'spec/app_config.yml' # load special foodsoft config require_relative 'support/coverage' # needs to be first -require File.expand_path("../../config/environment", __FILE__) +require File.expand_path('../config/environment', __dir__) require 'rspec/rails' require 'capybara/rails' require 'capybara/apparition' @@ -17,17 +17,17 @@ # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } +Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } RSpec.configure do |config| # We use capybara with webkit, and need database_cleaner - config.before(:each) do + config.before do DatabaseCleaner.strategy = (RSpec.current_example.metadata[:js] ? :truncation : :transaction) DatabaseCleaner.start # clean slate mail queues, not sure why needed - https://github.com/rspec/rspec-rails/issues/661 ActionMailer::Base.deliveries.clear end - config.after(:each) do + config.after do DatabaseCleaner.clean # Need to clear cache for RailsSettings::CachedSettings Rails.cache.clear @@ -35,7 +35,7 @@ # reload foodsoft configuration, so that tests can use FoodsoftConfig.config[:foo]=x # without messing up tests run after that - config.before(:each) do + config.before do FoodsoftConfig.init FoodsoftConfig.init_mailing end @@ -49,7 +49,7 @@ # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 - config.order = "random" + config.order = 'random' config.include SessionHelper, type: :feature diff --git a/spec/support/api_helper.rb b/spec/support/api_helper.rb index 65acc75b..752e19b6 100644 --- a/spec/support/api_helper.rb +++ b/spec/support/api_helper.rb @@ -4,12 +4,15 @@ module ApiHelper included do let(:user) { create(:user) } let(:api_scopes) { [] } # empty scopes for stricter testing (in reality this would be default_scopes) - let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token } + let(:api_access_token) do + create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token + end let(:api_authorization) { "Bearer #{api_access_token}" } def self.it_handles_invalid_token(method, path, params_block = -> { api_auth }) context 'with invalid access token' do let(:api_access_token) { 'abc' } + it { is_expected.to validate(method, path, 401, instance_exec(¶ms_block)) } end end @@ -17,6 +20,7 @@ def self.it_handles_invalid_token(method, path, params_block = -> { api_auth }) def self.it_handles_invalid_scope(method, path, params_block = -> { api_auth }) context 'with invalid scope' do let(:api_scopes) { ['none'] } + it { is_expected.to validate(method, path, 403, instance_exec(¶ms_block)) } end end diff --git a/spec/support/api_oauth.rb b/spec/support/api_oauth.rb index 8cf283f4..9d701349 100644 --- a/spec/support/api_oauth.rb +++ b/spec/support/api_oauth.rb @@ -5,7 +5,7 @@ module ApiOAuth included do let(:user) { build(:user) } let(:api_scopes) { [] } # empty scopes for stricter testing (in reality this would be default_scopes) - let(:api_access_token) { double(:acceptable? => true, :accessible? => true, scopes: api_scopes) } + let(:api_access_token) { double(acceptable?: true, accessible?: true, scopes: api_scopes) } before { allow(controller).to receive(:doorkeeper_token) { api_access_token } } before { allow(controller).to receive(:current_user) { user } } diff --git a/spec/support/coverage.rb b/spec/support/coverage.rb index 20bbdcf3..443da12d 100644 --- a/spec/support/coverage.rb +++ b/spec/support/coverage.rb @@ -11,16 +11,28 @@ # slightly tweaked coverage reporting def cov_no_plugins(source_file, path) - source_file.filename =~ /#{path}/ and not source_file.filename =~ /\/lib\/foodsoft_.*\// + source_file.filename =~ /#{path}/ and !(source_file.filename =~ %r{/lib/foodsoft_.*/}) end SimpleCov.start do add_filter '/spec/' add_filter '/test/' - add_group 'Models' do |s| cov_no_plugins s, '/app/models/' end - add_group 'Controllers' do |s| cov_no_plugins s, '/app/controllers/' end - add_group 'Helpers' do |s| cov_no_plugins s, '/app/helpers/' end - add_group 'Documents' do |s| cov_no_plugins s, '/app/documents/' end - add_group 'Libraries' do |s| cov_no_plugins s, '/lib/' end - add_group 'Plugins' do |s| s.filename =~ /\/lib\/foodsoft_.*\// end + add_group 'Models' do |s| + cov_no_plugins s, '/app/models/' + end + add_group 'Controllers' do |s| + cov_no_plugins s, '/app/controllers/' + end + add_group 'Helpers' do |s| + cov_no_plugins s, '/app/helpers/' + end + add_group 'Documents' do |s| + cov_no_plugins s, '/app/documents/' + end + add_group 'Libraries' do |s| + cov_no_plugins s, '/lib/' + end + add_group 'Plugins' do |s| + s.filename =~ %r{/lib/foodsoft_.*/} + end end end diff --git a/spec/support/faker.rb b/spec/support/faker.rb index 47441ca8..e4857f8a 100644 --- a/spec/support/faker.rb +++ b/spec/support/faker.rb @@ -1,8 +1,20 @@ module Faker + class LegacyUnit + class << self + def unit + %w[kg 1L 100ml piece bunch 500g].sample + end + end + end + class Unit class << self + def units + %w[KGM GRM LTR] + end + def unit - ['kg', '1L', '100ml', 'piece', 'bunch', '500g'].sample + units.sample end end end diff --git a/spec/support/integration.rb b/spec/support/integration.rb index 26add35a..6882ac5a 100644 --- a/spec/support/integration.rb +++ b/spec/support/integration.rb @@ -1,4 +1,4 @@ # @see http://stackoverflow.com/a/11048669/2866660 def scrolldown - page.execute_script "window.scrollBy(0,10000)" + page.execute_script 'window.scrollBy(0,10000)' end diff --git a/spec/support/session_helper.rb b/spec/support/session_helper.rb index 31fb0946..1075695e 100644 --- a/spec/support/session_helper.rb +++ b/spec/support/session_helper.rb @@ -1,14 +1,15 @@ module SessionHelper def login(user = nil, password = nil) visit login_path - user = FactoryBot.create :user if user.nil? + user = FactoryBot.create(:user) if user.nil? if user.instance_of? ::User - nick, password = user.nick, user.password + nick = user.nick + password = user.password else nick = user end - fill_in 'nick', :with => nick - fill_in 'password', :with => password + fill_in 'nick', with: nick + fill_in 'password', with: password find('input[type=submit]').click end end diff --git a/spec/support/shared_database.rb b/spec/support/shared_database.rb deleted file mode 100644 index f6c0ff0a..00000000 --- a/spec/support/shared_database.rb +++ /dev/null @@ -1,20 +0,0 @@ -ActiveSupport.on_load(:after_initialize) do - # We simulate the shared database by pointing to our own database. - # This allows running tests without additional database setup. - # But take care when designing tests using the shared database. - SharedSupplier.establish_connection Rails.env.to_sym - SharedArticle.establish_connection Rails.env.to_sym - # hack for different structure of shared database - SharedArticle.class_eval do - belongs_to :supplier, class_name: 'SharedSupplier' - alias_attribute :number, :order_number - alias_attribute :updated_on, :updated_at - def category - ArticleCategory.where(id: article_category_id).first - end - - def self.find_by_number(n) - find_by_order_number(n) - end - end -end