mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 00:14:50 +00:00
Merge pull request #15247 from frappe/hub-redesign
[encore] MarketPlace
This commit is contained in:
@@ -48,7 +48,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -133,7 +132,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -218,7 +216,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -303,7 +300,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -388,7 +384,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -473,7 +468,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -558,7 +552,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -643,7 +636,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -728,7 +720,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -813,7 +804,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -898,7 +888,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -983,7 +972,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1068,7 +1056,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1153,7 +1140,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1238,7 +1224,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1323,7 +1308,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1408,7 +1392,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1493,7 +1476,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1578,7 +1560,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1663,7 +1644,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1748,7 +1728,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1833,7 +1812,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -1918,7 +1896,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2003,7 +1980,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2088,7 +2064,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2173,7 +2148,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2258,7 +2232,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2343,7 +2316,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2428,7 +2400,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2513,7 +2484,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2598,7 +2568,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2683,7 +2652,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2768,7 +2736,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2853,7 +2820,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -2938,7 +2904,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3023,7 +2988,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3108,7 +3072,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3193,7 +3156,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3278,7 +3240,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3363,7 +3324,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3448,7 +3408,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3533,7 +3492,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3618,7 +3576,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3703,7 +3660,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3788,7 +3744,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3873,7 +3828,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -3958,7 +3912,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4043,7 +3996,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4128,7 +4080,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4213,7 +4164,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4298,7 +4248,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4383,7 +4332,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4468,7 +4416,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4553,7 +4500,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4638,7 +4584,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4723,7 +4668,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4808,7 +4752,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4893,7 +4836,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -4978,7 +4920,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -5063,7 +5004,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -5148,7 +5088,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -5233,7 +5172,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
@@ -5318,7 +5256,6 @@
|
|||||||
"naming_series": null,
|
"naming_series": null,
|
||||||
"net_weight": 0.0,
|
"net_weight": 0.0,
|
||||||
"opening_stock": 0.0,
|
"opening_stock": 0.0,
|
||||||
"publish_in_hub": 1,
|
|
||||||
"quality_parameters": [],
|
"quality_parameters": [],
|
||||||
"reorder_levels": [],
|
"reorder_levels": [],
|
||||||
"route": null,
|
"route": null,
|
||||||
|
|||||||
@@ -2,10 +2,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, requests, json
|
import frappe
|
||||||
from frappe.utils import now, nowdate, cint
|
|
||||||
from frappe.utils.nestedset import get_root_of
|
|
||||||
from frappe.contacts.doctype.contact.contact import get_default_contact
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def enable_hub():
|
def enable_hub():
|
||||||
@@ -15,263 +12,6 @@ def enable_hub():
|
|||||||
return hub_settings
|
return hub_settings
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=None):
|
def sync():
|
||||||
connection = get_client_connection()
|
|
||||||
filters = json.loads(filters)
|
|
||||||
|
|
||||||
response = connection.get_list(doctype,
|
|
||||||
limit_start=start, limit_page_length=limit,
|
|
||||||
filters=filters, fields=fields)
|
|
||||||
|
|
||||||
# Bad, need child tables in response
|
|
||||||
listing = []
|
|
||||||
for obj in response:
|
|
||||||
doc = connection.get_doc(doctype, obj['name'])
|
|
||||||
listing.append(doc)
|
|
||||||
|
|
||||||
return listing
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None):
|
|
||||||
doctype = 'Hub Item'
|
|
||||||
hub_settings = frappe.get_doc('Hub Settings')
|
hub_settings = frappe.get_doc('Hub Settings')
|
||||||
item_names_str = hub_settings.get('custom_data') or '[]'
|
hub_settings.sync()
|
||||||
item_names = json.loads(item_names_str)
|
|
||||||
filters = json.dumps({
|
|
||||||
'hub_item_code': ['in', item_names]
|
|
||||||
})
|
|
||||||
return get_list(doctype, start, limit, fields, filters, order_by)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def update_wishlist_item(item_name, remove=0):
|
|
||||||
remove = int(remove)
|
|
||||||
hub_settings = frappe.get_doc('Hub Settings')
|
|
||||||
data = hub_settings.get('custom_data')
|
|
||||||
if not data or not json.loads(data):
|
|
||||||
data = '[]'
|
|
||||||
hub_settings.custom_data = data
|
|
||||||
hub_settings.save()
|
|
||||||
|
|
||||||
item_names_str = data
|
|
||||||
item_names = json.loads(item_names_str)
|
|
||||||
if not remove and item_name not in item_names:
|
|
||||||
item_names.append(item_name)
|
|
||||||
if remove and item_name in item_names:
|
|
||||||
item_names.remove(item_name)
|
|
||||||
|
|
||||||
item_names_str = json.dumps(item_names)
|
|
||||||
|
|
||||||
hub_settings.custom_data = item_names_str
|
|
||||||
hub_settings.save()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_meta(doctype):
|
|
||||||
connection = get_client_connection()
|
|
||||||
meta = connection.get_doc('DocType', doctype)
|
|
||||||
categories = connection.get_list('Hub Category',
|
|
||||||
limit_start=0, limit_page_length=300,
|
|
||||||
filters={}, fields=['name'])
|
|
||||||
|
|
||||||
categories = [d.get('name') for d in categories]
|
|
||||||
return {
|
|
||||||
'meta': meta,
|
|
||||||
'companies': connection.get_list('Hub Company',
|
|
||||||
limit_start=0, limit_page_length=300,
|
|
||||||
filters={}, fields=['name']),
|
|
||||||
'categories': categories
|
|
||||||
}
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_categories(parent='All Categories'):
|
|
||||||
# get categories info with parent category and stuff
|
|
||||||
connection = get_client_connection()
|
|
||||||
categories = connection.get_list('Hub Category', filters={'parent_hub_category': parent})
|
|
||||||
|
|
||||||
response = [{'value': c.get('name'), 'expandable': c.get('is_group')} for c in categories]
|
|
||||||
return response
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def update_category(hub_item_code, category):
|
|
||||||
connection = get_hub_connection()
|
|
||||||
|
|
||||||
# args = frappe._dict(dict(
|
|
||||||
# doctype='Hub Category',
|
|
||||||
# hub_category_name=category
|
|
||||||
# ))
|
|
||||||
# response = connection.insert('Hub Category', args)
|
|
||||||
|
|
||||||
response = connection.update('Hub Item', frappe._dict(dict(
|
|
||||||
doctype='Hub Item',
|
|
||||||
hub_category = category
|
|
||||||
)), hub_item_code)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def send_review(hub_item_code, review):
|
|
||||||
review = json.loads(review)
|
|
||||||
hub_connection = get_hub_connection()
|
|
||||||
|
|
||||||
item_doc = hub_connection.connection.get_doc('Hub Item', hub_item_code)
|
|
||||||
existing_reviews = item_doc.get('reviews')
|
|
||||||
|
|
||||||
reviews = [review]
|
|
||||||
review.setdefault('idx', 0)
|
|
||||||
for r in existing_reviews:
|
|
||||||
if r.get('user') != review.get('user'):
|
|
||||||
reviews.append(r)
|
|
||||||
|
|
||||||
response = hub_connection.update('Hub Item', dict(
|
|
||||||
doctype='Hub Item',
|
|
||||||
reviews = reviews
|
|
||||||
), hub_item_code)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_details(hub_sync_id=None, doctype='Hub Item'):
|
|
||||||
if not hub_sync_id:
|
|
||||||
return
|
|
||||||
connection = get_client_connection()
|
|
||||||
details = connection.get_doc(doctype, hub_sync_id)
|
|
||||||
reviews = details.get('reviews')
|
|
||||||
if reviews and len(reviews):
|
|
||||||
for r in reviews:
|
|
||||||
r.setdefault('pretty_date', frappe.utils.pretty_date(r.get('modified')))
|
|
||||||
details.setdefault('reviews', reviews)
|
|
||||||
return details
|
|
||||||
|
|
||||||
def get_client_connection():
|
|
||||||
# frappeclient connection
|
|
||||||
hub_connection = get_hub_connection()
|
|
||||||
return hub_connection.connection
|
|
||||||
|
|
||||||
def get_hub_connection():
|
|
||||||
hub_connector = frappe.get_doc(
|
|
||||||
'Data Migration Connector', 'Hub Connector')
|
|
||||||
hub_connection = hub_connector.get_connection()
|
|
||||||
return hub_connection
|
|
||||||
|
|
||||||
def make_opportunity(buyer_name, email_id):
|
|
||||||
buyer_name = "HUB-" + buyer_name
|
|
||||||
|
|
||||||
if not frappe.db.exists('Lead', {'email_id': email_id}):
|
|
||||||
lead = frappe.new_doc("Lead")
|
|
||||||
lead.lead_name = buyer_name
|
|
||||||
lead.email_id = email_id
|
|
||||||
lead.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
o = frappe.new_doc("Opportunity")
|
|
||||||
o.enquiry_from = "Lead"
|
|
||||||
o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
|
|
||||||
o.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def make_rfq_and_send_opportunity(item, supplier):
|
|
||||||
supplier = make_supplier(supplier)
|
|
||||||
contact = make_contact(supplier)
|
|
||||||
item = make_item(item)
|
|
||||||
rfq = make_rfq(item, supplier, contact)
|
|
||||||
status = send_opportunity(contact)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'rfq': rfq,
|
|
||||||
'hub_document_created': status
|
|
||||||
}
|
|
||||||
|
|
||||||
def make_supplier(supplier):
|
|
||||||
# make supplier if not already exists
|
|
||||||
supplier = frappe._dict(json.loads(supplier))
|
|
||||||
|
|
||||||
if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}):
|
|
||||||
supplier_doc = frappe.get_doc({
|
|
||||||
'doctype': 'Supplier',
|
|
||||||
'supplier_name': supplier.supplier_name,
|
|
||||||
'supplier_group': supplier.supplier_group,
|
|
||||||
'supplier_email': supplier.supplier_email
|
|
||||||
}).insert()
|
|
||||||
else:
|
|
||||||
supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name)
|
|
||||||
|
|
||||||
return supplier_doc
|
|
||||||
|
|
||||||
def make_contact(supplier):
|
|
||||||
contact_name = get_default_contact('Supplier', supplier.supplier_name)
|
|
||||||
# make contact if not already exists
|
|
||||||
if not contact_name:
|
|
||||||
contact = frappe.get_doc({
|
|
||||||
'doctype': 'Contact',
|
|
||||||
'first_name': supplier.supplier_name,
|
|
||||||
'email_id': supplier.supplier_email,
|
|
||||||
'is_primary_contact': 1,
|
|
||||||
'links': [
|
|
||||||
{'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
|
|
||||||
]
|
|
||||||
}).insert()
|
|
||||||
else:
|
|
||||||
contact = frappe.get_doc('Contact', contact_name)
|
|
||||||
|
|
||||||
return contact
|
|
||||||
|
|
||||||
def make_item(item):
|
|
||||||
# make item if not already exists
|
|
||||||
item = frappe._dict(json.loads(item))
|
|
||||||
|
|
||||||
if not frappe.db.exists('Item', {'item_code': item.item_code}):
|
|
||||||
item_doc = frappe.get_doc({
|
|
||||||
'doctype': 'Item',
|
|
||||||
'item_code': item.item_code,
|
|
||||||
'item_group': item.item_group,
|
|
||||||
'is_item_from_hub': 1
|
|
||||||
}).insert()
|
|
||||||
else:
|
|
||||||
item_doc = frappe.get_doc('Item', item.item_code)
|
|
||||||
|
|
||||||
return item_doc
|
|
||||||
|
|
||||||
def make_rfq(item, supplier, contact):
|
|
||||||
# make rfq
|
|
||||||
rfq = frappe.get_doc({
|
|
||||||
'doctype': 'Request for Quotation',
|
|
||||||
'transaction_date': nowdate(),
|
|
||||||
'status': 'Draft',
|
|
||||||
'company': frappe.db.get_single_value('Hub Settings', 'company'),
|
|
||||||
'message_for_supplier': 'Please supply the specified items at the best possible rates',
|
|
||||||
'suppliers': [
|
|
||||||
{ 'supplier': supplier.name, 'contact': contact.name }
|
|
||||||
],
|
|
||||||
'items': [
|
|
||||||
{
|
|
||||||
'item_code': item.item_code,
|
|
||||||
'qty': 1,
|
|
||||||
'schedule_date': nowdate(),
|
|
||||||
'warehouse': item.default_warehouse or get_root_of("Warehouse"),
|
|
||||||
'description': item.description,
|
|
||||||
'uom': item.stock_uom
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
rfq.save()
|
|
||||||
rfq.submit()
|
|
||||||
return rfq
|
|
||||||
|
|
||||||
def send_opportunity(contact):
|
|
||||||
# Make Hub Message on Hub with lead data
|
|
||||||
doc = {
|
|
||||||
'doctype': 'Lead',
|
|
||||||
'lead_name': frappe.db.get_single_value('Hub Settings', 'company'),
|
|
||||||
'email_id': frappe.db.get_single_value('Hub Settings', 'user')
|
|
||||||
}
|
|
||||||
|
|
||||||
args = frappe._dict(dict(
|
|
||||||
doctype='Hub Message',
|
|
||||||
reference_doctype='Lead',
|
|
||||||
data=json.dumps(doc),
|
|
||||||
user=contact.email_id
|
|
||||||
))
|
|
||||||
|
|
||||||
connection = get_hub_connection()
|
|
||||||
response = connection.insert('Hub Message', args)
|
|
||||||
|
|
||||||
return response.ok
|
|
||||||
|
|||||||
169
erpnext/hub_node/api.py
Normal file
169
erpnext/hub_node/api.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe, json
|
||||||
|
import io, base64, os, requests
|
||||||
|
from frappe.frappeclient import FrappeClient
|
||||||
|
from frappe.desk.form.load import get_attachments
|
||||||
|
from frappe.utils.file_manager import get_file_path
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def call_hub_method(method, params=None):
|
||||||
|
connection = get_hub_connection()
|
||||||
|
|
||||||
|
if isinstance(params, string_types):
|
||||||
|
params = json.loads(params)
|
||||||
|
|
||||||
|
params.update({
|
||||||
|
'cmd': 'hub.hub.api.' + method
|
||||||
|
})
|
||||||
|
|
||||||
|
response = connection.post_request(params)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def map_fields(items):
|
||||||
|
field_mappings = get_field_mappings()
|
||||||
|
table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()]
|
||||||
|
|
||||||
|
hub_seller = frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
for fieldname in table_fields:
|
||||||
|
item.pop(fieldname, None)
|
||||||
|
|
||||||
|
for mapping in field_mappings:
|
||||||
|
local_fieldname = mapping.get('local_fieldname')
|
||||||
|
remote_fieldname = mapping.get('remote_fieldname')
|
||||||
|
|
||||||
|
value = item.get(local_fieldname)
|
||||||
|
item.pop(local_fieldname, None)
|
||||||
|
item[remote_fieldname] = value
|
||||||
|
|
||||||
|
item['doctype'] = 'Hub Item'
|
||||||
|
item['hub_seller'] = hub_seller
|
||||||
|
item.pop('attachments', None)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_valid_items(search_value=''):
|
||||||
|
items = frappe.get_list(
|
||||||
|
'Item',
|
||||||
|
fields=["*"],
|
||||||
|
filters={
|
||||||
|
'item_name': ['like', '%' + search_value + '%'],
|
||||||
|
'publish_in_hub': 0
|
||||||
|
},
|
||||||
|
order_by="modified desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
valid_items = filter(lambda x: x.image and x.description, items)
|
||||||
|
|
||||||
|
def prepare_item(item):
|
||||||
|
item.source_type = "local"
|
||||||
|
item.attachments = get_attachments('Item', item.item_code)
|
||||||
|
return item
|
||||||
|
|
||||||
|
valid_items = map(prepare_item, valid_items)
|
||||||
|
|
||||||
|
return valid_items
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def publish_selected_items(items_to_publish):
|
||||||
|
items_to_publish = json.loads(items_to_publish)
|
||||||
|
if not len(items_to_publish):
|
||||||
|
frappe.throw('No items to publish')
|
||||||
|
|
||||||
|
for item in items_to_publish:
|
||||||
|
item_code = item.get('item_code')
|
||||||
|
frappe.db.set_value('Item', item_code, 'publish_in_hub', 1)
|
||||||
|
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Hub Tracked Item',
|
||||||
|
'item_code': item_code,
|
||||||
|
'hub_category': item.get('hub_category'),
|
||||||
|
'image_list': item.get('image_list')
|
||||||
|
}).insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
|
|
||||||
|
items = map_fields(items_to_publish)
|
||||||
|
|
||||||
|
try:
|
||||||
|
item_sync_preprocess(len(items))
|
||||||
|
load_base64_image_from_items(items)
|
||||||
|
|
||||||
|
# TODO: Publish Progress
|
||||||
|
connection = get_hub_connection()
|
||||||
|
connection.insert_many(items)
|
||||||
|
|
||||||
|
item_sync_postprocess()
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(message=e, title='Hub Sync Error')
|
||||||
|
|
||||||
|
def item_sync_preprocess(intended_item_publish_count):
|
||||||
|
response = call_hub_method('pre_items_publish', {
|
||||||
|
'intended_item_publish_count': intended_item_publish_count
|
||||||
|
})
|
||||||
|
|
||||||
|
if response:
|
||||||
|
frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
frappe.throw('Unable to update remote activity')
|
||||||
|
|
||||||
|
def item_sync_postprocess():
|
||||||
|
response = call_hub_method('post_items_publish', {})
|
||||||
|
if response:
|
||||||
|
frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now())
|
||||||
|
else:
|
||||||
|
frappe.throw('Unable to update remote activity')
|
||||||
|
|
||||||
|
frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0)
|
||||||
|
|
||||||
|
|
||||||
|
def load_base64_image_from_items(items):
|
||||||
|
for item in items:
|
||||||
|
file_path = item['image']
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
|
base64content = None
|
||||||
|
|
||||||
|
if file_path.startswith('http'):
|
||||||
|
# fetch content and then base64 it
|
||||||
|
url = file_path
|
||||||
|
response = requests.get(url)
|
||||||
|
base64content = base64.b64encode(response.content)
|
||||||
|
else:
|
||||||
|
# read file then base64 it
|
||||||
|
file_path = os.path.abspath(get_file_path(file_path))
|
||||||
|
with io.open(file_path, 'rb') as f:
|
||||||
|
base64content = base64.b64encode(f.read())
|
||||||
|
|
||||||
|
image_data = json.dumps({
|
||||||
|
'file_name': file_name,
|
||||||
|
'base64': base64content
|
||||||
|
})
|
||||||
|
|
||||||
|
item['image'] = image_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_hub_connection():
|
||||||
|
read_only = True
|
||||||
|
|
||||||
|
if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
|
||||||
|
hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
|
||||||
|
|
||||||
|
# full rights to user who registered as hub_seller
|
||||||
|
if hub_connector.username == frappe.session.user:
|
||||||
|
read_only = False
|
||||||
|
|
||||||
|
if not read_only:
|
||||||
|
hub_connection = hub_connector.get_connection()
|
||||||
|
return hub_connection.connection
|
||||||
|
|
||||||
|
# read-only connection
|
||||||
|
if read_only:
|
||||||
|
hub_url = frappe.db.get_single_value('Hub Settings', 'hub_url')
|
||||||
|
hub_connection = FrappeClient(hub_url)
|
||||||
|
return hub_connection
|
||||||
|
|
||||||
|
def get_field_mappings():
|
||||||
|
return []
|
||||||
@@ -16,14 +16,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"is_child_table": 0,
|
"is_child_table": 0,
|
||||||
"local_fieldname": "eval:frappe.db.get_default(\"company\")",
|
"local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')",
|
||||||
"remote_fieldname": "company_name"
|
"remote_fieldname": "hub_seller"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"is_child_table": 0,
|
"is_child_table": 0,
|
||||||
"local_fieldname": "image",
|
"local_fieldname": "image",
|
||||||
"remote_fieldname": "image"
|
"remote_fieldname": "image"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"is_child_table": 0,
|
||||||
|
"local_fieldname": "image_list",
|
||||||
|
"remote_fieldname": "image_list"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"is_child_table": 0,
|
"is_child_table": 0,
|
||||||
"local_fieldname": "item_group",
|
"local_fieldname": "item_group",
|
||||||
@@ -31,13 +36,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"is_child_table": 0,
|
"is_child_table": 0,
|
||||||
"local_fieldname": "eval:frappe.session.user",
|
"local_fieldname": "hub_category",
|
||||||
"remote_fieldname": "seller"
|
"remote_fieldname": "hub_category"
|
||||||
},
|
|
||||||
{
|
|
||||||
"is_child_table": 0,
|
|
||||||
"local_fieldname": "eval:frappe.db.get_default(\"country\")",
|
|
||||||
"remote_fieldname": "country"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
"mapping_name": "Item to Hub Item",
|
"mapping_name": "Item to Hub Item",
|
||||||
"mapping_type": "Push",
|
"mapping_type": "Push",
|
||||||
"migration_id_field": "hub_sync_id",
|
"migration_id_field": "hub_sync_id",
|
||||||
"modified": "2018-02-14 15:57:05.595712",
|
"modified": "2018-08-19 22:20:25.727581",
|
||||||
"modified_by": "achilles@erpnext.com",
|
"modified_by": "Administrator",
|
||||||
"name": "Item to Hub Item",
|
"name": "Item to Hub Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"page_length": 10,
|
"page_length": 10,
|
||||||
|
|||||||
@@ -7,16 +7,13 @@
|
|||||||
{
|
{
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
"mapping": "Item to Hub Item"
|
"mapping": "Item to Hub Item"
|
||||||
},
|
|
||||||
{
|
|
||||||
"enabled": 1,
|
|
||||||
"mapping": "Hub Message to Lead"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2018-02-14 15:57:05.519715",
|
"modified": "2018-08-19 22:20:25.644602",
|
||||||
"modified_by": "achilles@erpnext.com",
|
"modified_by": "Administrator",
|
||||||
"module": "Hub Node",
|
"module": "Hub Node",
|
||||||
"name": "Hub Sync",
|
"name": "Hub Sync",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"plan_name": "Hub Sync"
|
"plan_name": "Hub Sync",
|
||||||
|
"postprocess_method": "erpnext.hub_node.api.item_sync_postprocess"
|
||||||
}
|
}
|
||||||
@@ -1,177 +1,3 @@
|
|||||||
frappe.ui.form.on("Hub Settings", {
|
frappe.ui.form.on("Hub Settings", {
|
||||||
refresh: function(frm) {
|
onload_post_render: function() {},
|
||||||
frm.add_custom_button(__('Logs'),
|
|
||||||
() => frappe.set_route('List', 'Data Migration Run', {
|
|
||||||
data_migration_plan: 'Hub Sync'
|
|
||||||
}));
|
|
||||||
|
|
||||||
frm.trigger("enabled");
|
|
||||||
|
|
||||||
if (frm.doc.enabled) {
|
|
||||||
frm.add_custom_button(__('Sync'),
|
|
||||||
() => frm.call('sync'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onload: function(frm) {
|
|
||||||
let token = frappe.urllib.get_arg("access_token");
|
|
||||||
if(token) {
|
|
||||||
let email = frm.get_field("user");
|
|
||||||
console.log('token', frappe.urllib.get_arg("access_token"));
|
|
||||||
|
|
||||||
get_user_details(frm, token, email);
|
|
||||||
let row = frappe.model.add_child(frm.doc, "Hub Users", "users");
|
|
||||||
row.user = frappe.session.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!frm.doc.country) {
|
|
||||||
frm.set_value("country", frappe.defaults.get_default("Country"));
|
|
||||||
}
|
|
||||||
if(!frm.doc.company) {
|
|
||||||
frm.set_value("company", frappe.defaults.get_default("Company"));
|
|
||||||
}
|
|
||||||
if(!frm.doc.user) {
|
|
||||||
frm.set_value("user", frappe.session.user);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onload_post_render: function(frm) {
|
|
||||||
if(frm.get_field("unregister_from_hub").$input)
|
|
||||||
frm.get_field("unregister_from_hub").$input.addClass("btn-danger");
|
|
||||||
},
|
|
||||||
on_update: function(frm) {
|
|
||||||
},
|
|
||||||
enabled: function(frm) {
|
|
||||||
if(!frm.doc.enabled) {
|
|
||||||
frm.trigger("set_enable_hub_primary_button");
|
|
||||||
} else {
|
|
||||||
frm.page.set_primary_action(__("Save Settings"), () => {
|
|
||||||
frm.save();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hub_user_email: function(frm) {
|
|
||||||
if(frm.doc.hub_user_email){
|
|
||||||
frm.set_value("hub_user_name", frappe.user.full_name(frm.doc.hub_user_email));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
set_enable_hub_primary_button: (frm) => {
|
|
||||||
frm.page.set_primary_action(__("Enable Hub"), () => {
|
|
||||||
if(frappe.session.user === "Administrator") {
|
|
||||||
frappe.msgprint(__("Please login as another user."))
|
|
||||||
} else {
|
|
||||||
// frappe.verify_password(() => {
|
|
||||||
|
|
||||||
// } );
|
|
||||||
|
|
||||||
frm.trigger("call_pre_reg");
|
|
||||||
// frm.trigger("call_register");
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
call_pre_reg: (frm) => {
|
|
||||||
this.frm.call({
|
|
||||||
doc: this.frm.doc,
|
|
||||||
method: "pre_reg",
|
|
||||||
args: {},
|
|
||||||
freeze: true,
|
|
||||||
callback: function(r) {
|
|
||||||
console.log(r.message);
|
|
||||||
authorize(frm, r.message.client_id, r.message.redirect_uri);
|
|
||||||
},
|
|
||||||
onerror: function() {
|
|
||||||
frappe.msgprint(__("Wrong Password"));
|
|
||||||
frm.set_value("enabled", 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
call_register: (frm) => {
|
|
||||||
this.frm.call({
|
|
||||||
doc: this.frm.doc,
|
|
||||||
method: "register",
|
|
||||||
args: {},
|
|
||||||
freeze: true,
|
|
||||||
callback: function(r) {},
|
|
||||||
onerror: function() {
|
|
||||||
frappe.msgprint(__("Wrong Password"));
|
|
||||||
frm.set_value("enabled", 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
unregister_from_hub: (frm) => {
|
|
||||||
frappe.verify_password(() => {
|
|
||||||
var d = frappe.confirm(__('Are you sure you want to unregister?'), () => {
|
|
||||||
frm.call('unregister');
|
|
||||||
}, () => {}, __('Confirm Action'));
|
|
||||||
d.get_primary_btn().addClass("btn-danger");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// let hub_url = 'https://hubmarket.org'
|
|
||||||
let hub_url = 'http://159.89.175.122'
|
|
||||||
// let hub_url = 'http://erpnext.hub:8000'
|
|
||||||
|
|
||||||
function authorize(frm, client_id, redirect_uri) {
|
|
||||||
|
|
||||||
// queryStringData is details of OAuth Client (Implicit Grant) on Custom App
|
|
||||||
var queryStringData = {
|
|
||||||
response_type : "token",
|
|
||||||
client_id : client_id,
|
|
||||||
redirect_uri : redirect_uri
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current raw route and build url
|
|
||||||
const route = "/desk#" + frappe.get_raw_route_str();
|
|
||||||
localStorage.removeItem("route"); // Clear previously set route if any
|
|
||||||
localStorage.setItem("route", route);
|
|
||||||
|
|
||||||
// Go authorize!
|
|
||||||
let api_route = "/api/method/frappe.integrations.oauth2.authorize?";
|
|
||||||
let url = hub_url + api_route + $.param(queryStringData);
|
|
||||||
window.location.replace(url, 'test');
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_user_details(frm, token, email) {
|
|
||||||
console.log('user_details');
|
|
||||||
var route = localStorage.getItem("route");
|
|
||||||
if (token && route) {
|
|
||||||
// Clean up access token from route
|
|
||||||
frappe.set_route(frappe.get_route().join("/"))
|
|
||||||
|
|
||||||
// query protected resource e.g. Hub Items with token
|
|
||||||
var call = {
|
|
||||||
"async": true,
|
|
||||||
"crossDomain": true,
|
|
||||||
"url": hub_url + "/api/resource/User",
|
|
||||||
"method": "GET",
|
|
||||||
"data": {
|
|
||||||
// "email": email,
|
|
||||||
"fields": '["name", "first_name", "language"]',
|
|
||||||
"limit_page_length": 1
|
|
||||||
},
|
|
||||||
"headers": {
|
|
||||||
"authorization": "Bearer " + token,
|
|
||||||
"content-type": "application/x-www-form-urlencoded"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$.ajax(call).done(function (response) {
|
|
||||||
// display openid profile
|
|
||||||
console.log('response', response);
|
|
||||||
|
|
||||||
let data = response.data[0];
|
|
||||||
frm.set_value("enabled", 1);
|
|
||||||
frm.set_value("hub_username", data.first_name);
|
|
||||||
frm.set_value("hub_user_status", "Starter");
|
|
||||||
frm.set_value("language", data.language);
|
|
||||||
frm.save();
|
|
||||||
|
|
||||||
// clear route from localStorage
|
|
||||||
localStorage.removeItem("route");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,74 +14,13 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "enabled",
|
"default": "https://hubmarket.org",
|
||||||
"fieldtype": "Check",
|
"fieldname": "hub_url",
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Enabled",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "suspended",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Suspended",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "enabled",
|
|
||||||
"fieldname": "hub_username",
|
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
@@ -90,70 +29,7 @@
|
|||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Hub Username",
|
"label": "Hub URL",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "user",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "User",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "User",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_0",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
@@ -171,108 +47,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"depends_on": "enabled",
|
"fieldname": "registered",
|
||||||
"fieldname": "hub_user_status",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Status",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "enabled",
|
|
||||||
"fieldname": "language",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Language",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 1,
|
|
||||||
"collapsible_depends_on": "eval:(!doc.enabled)",
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fieldname": "seller_profile_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Company and Seller Profile",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "company_registered",
|
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
@@ -281,14 +61,14 @@
|
|||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Company Registered",
|
"label": "Registered",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
@@ -299,6 +79,39 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "sync_in_progress",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Sync in Progress",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -331,6 +144,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -362,6 +176,39 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "site_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Site Name",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -394,11 +241,44 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "company_logo",
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Currency",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "logo",
|
||||||
"fieldtype": "Attach Image",
|
"fieldtype": "Attach Image",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
@@ -425,11 +305,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "seller_description",
|
"fieldname": "company_description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
@@ -456,234 +337,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "users_sb",
|
"depends_on": "",
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Enabled Users",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "users",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Users",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Hub Users",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "enabled",
|
|
||||||
"fieldname": "publish_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Publish",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "publish",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Publish Items to Hub",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "publish",
|
|
||||||
"fieldname": "publish_pricing",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Publish Pricing",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:(doc.publish && doc.publish_pricing)",
|
|
||||||
"fieldname": "selling_price_list",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Selling Price List",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Price List",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "publish",
|
|
||||||
"fieldname": "publish_availability",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Publish Availability",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "publish",
|
|
||||||
"fieldname": "last_sync_datetime",
|
"fieldname": "last_sync_datetime",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -700,7 +359,7 @@
|
|||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
@@ -711,6 +370,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -744,6 +404,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@@ -777,6 +438,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -817,8 +479,8 @@
|
|||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-03-26 00:55:17.929140",
|
"modified": "2018-08-29 17:46:30.413159",
|
||||||
"modified_by": "test1@example.com",
|
"modified_by": "Administrator",
|
||||||
"module": "Hub Node",
|
"module": "Hub Node",
|
||||||
"name": "Hub Settings",
|
"name": "Hub Settings",
|
||||||
"name_case": "",
|
"name_case": "",
|
||||||
@@ -826,7 +488,6 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 0,
|
"delete": 0,
|
||||||
@@ -852,5 +513,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, requests, json
|
import frappe, requests, json, time
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_years, now, get_datetime, get_datetime_str
|
from frappe.utils import add_years, now, get_datetime, get_datetime_str
|
||||||
@@ -10,107 +10,65 @@ from frappe import _
|
|||||||
from erpnext.utilities.product import get_price, get_qty_in_stock
|
from erpnext.utilities.product import get_price, get_qty_in_stock
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
hub_url = "https://hubmarket.org"
|
|
||||||
# hub_url = "http://159.89.175.122"
|
|
||||||
# hub_url = "http://erpnext.hub:8000"
|
|
||||||
|
|
||||||
class OAuth2Session():
|
|
||||||
def __init__(self, headers):
|
|
||||||
self.headers = headers
|
|
||||||
def get(self, url, params, headers, verify):
|
|
||||||
res = requests.get(url, params=params, headers=self.headers, verify=verify)
|
|
||||||
return res
|
|
||||||
def post(self, url, data, verify):
|
|
||||||
res = requests.post(url, data=data, headers=self.headers, verify=verify)
|
|
||||||
return res
|
|
||||||
def put(self, url, data, verify):
|
|
||||||
res = requests.put(url, data=data, headers=self.headers, verify=verify)
|
|
||||||
return res
|
|
||||||
|
|
||||||
class HubSetupError(frappe.ValidationError): pass
|
class HubSetupError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class HubSettings(Document):
|
class HubSettings(Document):
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.publish_pricing and not self.selling_price_list:
|
self.site_name = frappe.utils.get_url()
|
||||||
frappe.throw(_("Please select a Price List to publish pricing"))
|
|
||||||
|
|
||||||
def get_hub_url(self):
|
def get_hub_url(self):
|
||||||
return hub_url
|
return self.hub_url
|
||||||
|
|
||||||
def sync(self):
|
|
||||||
"""Create and execute Data Migration Run for Hub Sync plan"""
|
|
||||||
frappe.has_permission('Hub Settings', throw=True)
|
|
||||||
|
|
||||||
doc = frappe.get_doc({
|
|
||||||
'doctype': 'Data Migration Run',
|
|
||||||
'data_migration_plan': 'Hub Sync',
|
|
||||||
'data_migration_connector': 'Hub Connector'
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
doc.run()
|
|
||||||
|
|
||||||
def pre_reg(self):
|
|
||||||
site_name = frappe.local.site + ':' + str(frappe.conf.webserver_port)
|
|
||||||
protocol = 'http://'
|
|
||||||
route = '/token'
|
|
||||||
data = {
|
|
||||||
'site_name': site_name,
|
|
||||||
'protocol': protocol,
|
|
||||||
'route': route
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect_url = protocol + site_name + route
|
|
||||||
post_url = hub_url + '/api/method/hub.hub.api.pre_reg'
|
|
||||||
|
|
||||||
response = requests.post(post_url, data=data)
|
|
||||||
response.raise_for_status()
|
|
||||||
message = response.json().get('message')
|
|
||||||
|
|
||||||
if message and message.get('client_id'):
|
|
||||||
print("======CLIENT_ID======")
|
|
||||||
print(message.get('client_id'))
|
|
||||||
|
|
||||||
return {
|
|
||||||
'client_id': message.get('client_id'),
|
|
||||||
'redirect_uri': redirect_url
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
""" Create a User on hub.erpnext.org and return username/password """
|
""" Create a User on hub.erpnext.org and return username/password """
|
||||||
|
|
||||||
|
if frappe.session.user == 'Administrator':
|
||||||
|
frappe.throw(_('Please login as another user to register on Marketplace'))
|
||||||
|
|
||||||
|
if 'System Manager' not in frappe.get_roles():
|
||||||
|
frappe.throw(_('Only users with System Manager role can register on Marketplace'), frappe.PermissionError)
|
||||||
|
|
||||||
|
self.site_name = frappe.utils.get_url()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'email': frappe.session.user
|
'profile': self.as_json()
|
||||||
}
|
}
|
||||||
post_url = hub_url + '/api/method/hub.hub.api.register'
|
post_url = self.get_hub_url() + '/api/method/hub.hub.api.register'
|
||||||
|
|
||||||
|
response = requests.post(post_url, data=data, headers = {'accept': 'application/json'})
|
||||||
|
|
||||||
response = requests.post(post_url, data=data)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
message = response.json().get('message')
|
|
||||||
|
|
||||||
if message and message.get('password'):
|
if response.ok:
|
||||||
self.user = frappe.session.user
|
message = response.json().get('message')
|
||||||
|
else:
|
||||||
|
frappe.throw(json.loads(response.text))
|
||||||
|
|
||||||
|
if message.get('email'):
|
||||||
self.create_hub_connector(message)
|
self.create_hub_connector(message)
|
||||||
self.company = frappe.defaults.get_user_default('company')
|
self.registered = 1
|
||||||
self.enabled = 1
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def unregister(self):
|
return message or None
|
||||||
""" Disable the User on hub.erpnext.org"""
|
|
||||||
|
|
||||||
hub_connector = frappe.get_doc(
|
# def unregister(self):
|
||||||
'Data Migration Connector', 'Hub Connector')
|
# """ Disable the User on hub.erpnext.org"""
|
||||||
|
|
||||||
connection = hub_connector.get_connection()
|
# hub_connector = frappe.get_doc(
|
||||||
response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username)
|
# 'Data Migration Connector', 'Hub Connector')
|
||||||
|
|
||||||
if response_doc['enabled'] == 0:
|
# connection = hub_connector.get_connection()
|
||||||
self.enabled = 0
|
# response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username)
|
||||||
self.save()
|
|
||||||
|
# if response_doc['enabled'] == 0:
|
||||||
|
# self.enabled = 0
|
||||||
|
# self.save()
|
||||||
|
|
||||||
def create_hub_connector(self, message):
|
def create_hub_connector(self, message):
|
||||||
if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
|
if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
|
||||||
hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
|
hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
|
||||||
|
hub_connector.hostname = self.get_hub_url()
|
||||||
hub_connector.username = message['email']
|
hub_connector.username = message['email']
|
||||||
hub_connector.password = message['password']
|
hub_connector.password = message['password']
|
||||||
hub_connector.save()
|
hub_connector.save()
|
||||||
@@ -120,7 +78,7 @@ class HubSettings(Document):
|
|||||||
'doctype': 'Data Migration Connector',
|
'doctype': 'Data Migration Connector',
|
||||||
'connector_type': 'Frappe',
|
'connector_type': 'Frappe',
|
||||||
'connector_name': 'Hub Connector',
|
'connector_name': 'Hub Connector',
|
||||||
'hostname': hub_url,
|
'hostname': self.get_hub_url(),
|
||||||
'username': message['email'],
|
'username': message['email'],
|
||||||
'password': message['password']
|
'password': message['password']
|
||||||
}).insert()
|
}).insert()
|
||||||
@@ -140,6 +98,9 @@ def reset_hub_settings(last_sync_datetime = ""):
|
|||||||
frappe.msgprint(_("Successfully unregistered."))
|
frappe.msgprint(_("Successfully unregistered."))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def sync():
|
def register_seller(**kwargs):
|
||||||
hub_settings = frappe.get_doc('Hub Settings')
|
settings = frappe.get_doc('Hub Settings')
|
||||||
hub_settings.sync()
|
settings.update(kwargs)
|
||||||
|
message = settings.register()
|
||||||
|
|
||||||
|
return message.get('email')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
|
"autoname": "field:item_code",
|
||||||
"beta": 0,
|
"beta": 0,
|
||||||
"creation": "2018-03-18 09:33:50.267762",
|
"creation": "2018-03-18 09:33:50.267762",
|
||||||
"custom": 0,
|
"custom": 0,
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -41,6 +43,70 @@
|
|||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "hub_category",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Hub Category",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "image_list",
|
||||||
|
"fieldtype": "Long Text",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Image List",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -49,12 +115,12 @@
|
|||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"image_view": 0,
|
"image_view": 0,
|
||||||
"in_create": 1,
|
"in_create": 0,
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-03-18 09:34:01.757713",
|
"modified": "2018-08-19 22:24:06.207307",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Hub Node",
|
"module": "Hub Node",
|
||||||
"name": "Hub Tracked Item",
|
"name": "Hub Tracked Item",
|
||||||
@@ -63,7 +129,6 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
@@ -89,5 +154,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Hub Tracked Item", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Hub Tracked Item
|
|
||||||
() => frappe.tests.make('Hub Tracked Item', [
|
|
||||||
// values to be set
|
|
||||||
{key: 'value'}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.key, 'value');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
143
erpnext/hub_node/legacy.py
Normal file
143
erpnext/hub_node/legacy.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe, json
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
from frappe.frappeclient import FrappeClient
|
||||||
|
from frappe.utils.nestedset import get_root_of
|
||||||
|
from frappe.contacts.doctype.contact.contact import get_default_contact
|
||||||
|
|
||||||
|
def get_list(doctype, start, limit, fields, filters, order_by):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_hub_connection():
|
||||||
|
if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
|
||||||
|
hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
|
||||||
|
hub_connection = hub_connector.get_connection()
|
||||||
|
return hub_connection.connection
|
||||||
|
|
||||||
|
# read-only connection
|
||||||
|
hub_connection = FrappeClient(frappe.conf.hub_url)
|
||||||
|
return hub_connection
|
||||||
|
|
||||||
|
def make_opportunity(buyer_name, email_id):
|
||||||
|
buyer_name = "HUB-" + buyer_name
|
||||||
|
|
||||||
|
if not frappe.db.exists('Lead', {'email_id': email_id}):
|
||||||
|
lead = frappe.new_doc("Lead")
|
||||||
|
lead.lead_name = buyer_name
|
||||||
|
lead.email_id = email_id
|
||||||
|
lead.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
o = frappe.new_doc("Opportunity")
|
||||||
|
o.enquiry_from = "Lead"
|
||||||
|
o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
|
||||||
|
o.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_rfq_and_send_opportunity(item, supplier):
|
||||||
|
supplier = make_supplier(supplier)
|
||||||
|
contact = make_contact(supplier)
|
||||||
|
item = make_item(item)
|
||||||
|
rfq = make_rfq(item, supplier, contact)
|
||||||
|
status = send_opportunity(contact)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'rfq': rfq,
|
||||||
|
'hub_document_created': status
|
||||||
|
}
|
||||||
|
|
||||||
|
def make_supplier(supplier):
|
||||||
|
# make supplier if not already exists
|
||||||
|
supplier = frappe._dict(json.loads(supplier))
|
||||||
|
|
||||||
|
if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}):
|
||||||
|
supplier_doc = frappe.get_doc({
|
||||||
|
'doctype': 'Supplier',
|
||||||
|
'supplier_name': supplier.supplier_name,
|
||||||
|
'supplier_group': supplier.supplier_group,
|
||||||
|
'supplier_email': supplier.supplier_email
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name)
|
||||||
|
|
||||||
|
return supplier_doc
|
||||||
|
|
||||||
|
def make_contact(supplier):
|
||||||
|
contact_name = get_default_contact('Supplier', supplier.supplier_name)
|
||||||
|
# make contact if not already exists
|
||||||
|
if not contact_name:
|
||||||
|
contact = frappe.get_doc({
|
||||||
|
'doctype': 'Contact',
|
||||||
|
'first_name': supplier.supplier_name,
|
||||||
|
'email_id': supplier.supplier_email,
|
||||||
|
'is_primary_contact': 1,
|
||||||
|
'links': [
|
||||||
|
{'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
|
||||||
|
]
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
contact = frappe.get_doc('Contact', contact_name)
|
||||||
|
|
||||||
|
return contact
|
||||||
|
|
||||||
|
def make_item(item):
|
||||||
|
# make item if not already exists
|
||||||
|
item = frappe._dict(json.loads(item))
|
||||||
|
|
||||||
|
if not frappe.db.exists('Item', {'item_code': item.item_code}):
|
||||||
|
item_doc = frappe.get_doc({
|
||||||
|
'doctype': 'Item',
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'item_group': item.item_group,
|
||||||
|
'is_item_from_hub': 1
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
item_doc = frappe.get_doc('Item', item.item_code)
|
||||||
|
|
||||||
|
return item_doc
|
||||||
|
|
||||||
|
def make_rfq(item, supplier, contact):
|
||||||
|
# make rfq
|
||||||
|
rfq = frappe.get_doc({
|
||||||
|
'doctype': 'Request for Quotation',
|
||||||
|
'transaction_date': nowdate(),
|
||||||
|
'status': 'Draft',
|
||||||
|
'company': frappe.db.get_single_value('Hub Settings', 'company'),
|
||||||
|
'message_for_supplier': 'Please supply the specified items at the best possible rates',
|
||||||
|
'suppliers': [
|
||||||
|
{ 'supplier': supplier.name, 'contact': contact.name }
|
||||||
|
],
|
||||||
|
'items': [
|
||||||
|
{
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'qty': 1,
|
||||||
|
'schedule_date': nowdate(),
|
||||||
|
'warehouse': item.default_warehouse or get_root_of("Warehouse"),
|
||||||
|
'description': item.description,
|
||||||
|
'uom': item.stock_uom
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
rfq.save()
|
||||||
|
rfq.submit()
|
||||||
|
return rfq
|
||||||
|
|
||||||
|
def send_opportunity(contact):
|
||||||
|
# Make Hub Message on Hub with lead data
|
||||||
|
doc = {
|
||||||
|
'doctype': 'Lead',
|
||||||
|
'lead_name': frappe.db.get_single_value('Hub Settings', 'company'),
|
||||||
|
'email_id': frappe.db.get_single_value('Hub Settings', 'user')
|
||||||
|
}
|
||||||
|
|
||||||
|
args = frappe._dict(dict(
|
||||||
|
doctype='Hub Message',
|
||||||
|
reference_doctype='Lead',
|
||||||
|
data=json.dumps(doc),
|
||||||
|
user=contact.email_id
|
||||||
|
))
|
||||||
|
|
||||||
|
connection = get_hub_connection()
|
||||||
|
response = connection.insert('Hub Message', args)
|
||||||
|
|
||||||
|
return response.ok
|
||||||
@@ -557,5 +557,8 @@ erpnext.patches.v11_0.set_department_for_doctypes
|
|||||||
erpnext.patches.v11_0.update_allow_transfer_for_manufacture
|
erpnext.patches.v11_0.update_allow_transfer_for_manufacture
|
||||||
erpnext.patches.v11_0.add_item_group_defaults
|
erpnext.patches.v11_0.add_item_group_defaults
|
||||||
erpnext.patches.v10_0.update_address_template_for_india
|
erpnext.patches.v10_0.update_address_template_for_india
|
||||||
|
execute:frappe.delete_doc("Page", "hub")
|
||||||
|
erpnext.patches.v11_0.reset_publish_in_hub_for_all_items
|
||||||
|
erpnext.patches.v11_0.update_hub_url
|
||||||
erpnext.patches.v10_0.set_discount_amount
|
erpnext.patches.v10_0.set_discount_amount
|
||||||
erpnext.patches.v10_0.recalculate_gross_margin_for_project
|
erpnext.patches.v10_0.recalculate_gross_margin_for_project
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc('stock', 'doctype', 'item')
|
||||||
|
frappe.db.sql("""update `tabItem` set publish_in_hub = 0""")
|
||||||
5
erpnext/patches/v11_0/update_hub_url.py
Normal file
5
erpnext/patches/v11_0/update_hub_url.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc('hub_node', 'doctype', 'Hub Settings')
|
||||||
|
frappe.db.set_value('Hub Settings', 'Hub Settings', 'hub_url', 'https://hubmarket.org')
|
||||||
@@ -1,52 +1,55 @@
|
|||||||
{
|
{
|
||||||
"css/erpnext.css": [
|
"css/erpnext.css": [
|
||||||
"public/less/erpnext.less",
|
"public/less/erpnext.less",
|
||||||
"public/less/hub.less"
|
"public/less/hub.less"
|
||||||
],
|
],
|
||||||
"js/erpnext-web.min.js": [
|
"js/erpnext-web.min.js": [
|
||||||
"public/js/website_utils.js",
|
"public/js/website_utils.js",
|
||||||
"public/js/shopping_cart.js"
|
"public/js/shopping_cart.js"
|
||||||
],
|
],
|
||||||
"css/erpnext-web.css": [
|
"css/erpnext-web.css": [
|
||||||
"public/less/website.less"
|
"public/less/website.less"
|
||||||
],
|
],
|
||||||
"js/erpnext.min.js": [
|
"js/marketplace.min.js": [
|
||||||
"public/js/conf.js",
|
"public/js/hub/marketplace.js"
|
||||||
"public/js/utils.js",
|
],
|
||||||
"public/js/queries.js",
|
"js/erpnext.min.js": [
|
||||||
"public/js/sms_manager.js",
|
"public/js/conf.js",
|
||||||
"public/js/utils/party.js",
|
"public/js/utils.js",
|
||||||
"public/js/templates/address_list.html",
|
"public/js/queries.js",
|
||||||
"public/js/templates/contact_list.html",
|
"public/js/sms_manager.js",
|
||||||
"public/js/controllers/stock_controller.js",
|
"public/js/utils/party.js",
|
||||||
"public/js/payment/payments.js",
|
"public/js/templates/address_list.html",
|
||||||
"public/js/controllers/taxes_and_totals.js",
|
"public/js/templates/contact_list.html",
|
||||||
"public/js/controllers/transaction.js",
|
"public/js/controllers/stock_controller.js",
|
||||||
"public/js/pos/pos.html",
|
"public/js/payment/payments.js",
|
||||||
"public/js/pos/pos_bill_item.html",
|
"public/js/controllers/taxes_and_totals.js",
|
||||||
"public/js/pos/pos_bill_item_new.html",
|
"public/js/controllers/transaction.js",
|
||||||
"public/js/pos/pos_selected_item.html",
|
"public/js/pos/pos.html",
|
||||||
"public/js/pos/pos_item.html",
|
"public/js/pos/pos_bill_item.html",
|
||||||
"public/js/pos/pos_tax_row.html",
|
"public/js/pos/pos_bill_item_new.html",
|
||||||
"public/js/pos/customer_toolbar.html",
|
"public/js/pos/pos_selected_item.html",
|
||||||
"public/js/pos/pos_invoice_list.html",
|
"public/js/pos/pos_item.html",
|
||||||
"public/js/payment/pos_payment.html",
|
"public/js/pos/pos_tax_row.html",
|
||||||
"public/js/payment/payment_details.html",
|
"public/js/pos/customer_toolbar.html",
|
||||||
"public/js/templates/item_selector.html",
|
"public/js/pos/pos_invoice_list.html",
|
||||||
|
"public/js/payment/pos_payment.html",
|
||||||
|
"public/js/payment/payment_details.html",
|
||||||
|
"public/js/templates/item_selector.html",
|
||||||
"public/js/templates/employees_to_mark_attendance.html",
|
"public/js/templates/employees_to_mark_attendance.html",
|
||||||
"public/js/utils/item_selector.js",
|
"public/js/utils/item_selector.js",
|
||||||
"public/js/help_links.js",
|
"public/js/help_links.js",
|
||||||
"public/js/agriculture/ternary_plot.js",
|
"public/js/agriculture/ternary_plot.js",
|
||||||
"public/js/templates/item_quick_entry.html",
|
"public/js/templates/item_quick_entry.html",
|
||||||
"public/js/utils/item_quick_entry.js",
|
"public/js/utils/item_quick_entry.js",
|
||||||
"public/js/utils/customer_quick_entry.js",
|
"public/js/utils/customer_quick_entry.js",
|
||||||
"public/js/education/student_button.html",
|
"public/js/education/student_button.html",
|
||||||
"public/js/education/assessment_result_tool.html",
|
"public/js/education/assessment_result_tool.html",
|
||||||
"public/js/hub/hub_factory.js"
|
"public/js/hub/hub_factory.js"
|
||||||
],
|
],
|
||||||
"js/item-dashboard.min.js": [
|
"js/item-dashboard.min.js": [
|
||||||
"stock/dashboard/item_dashboard.html",
|
"stock/dashboard/item_dashboard.html",
|
||||||
"stock/dashboard/item_dashboard_list.html",
|
"stock/dashboard/item_dashboard_list.html",
|
||||||
"stock/dashboard/item_dashboard.js"
|
"stock/dashboard/item_dashboard.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
101
erpnext/public/js/hub/PageContainer.vue
Normal file
101
erpnext/public/js/hub/PageContainer.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hub-page-container">
|
||||||
|
<component :is="current_page"></component>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Home from './pages/Home.vue';
|
||||||
|
import Search from './pages/Search.vue';
|
||||||
|
import Category from './pages/Category.vue';
|
||||||
|
import SavedItems from './pages/SavedItems.vue';
|
||||||
|
import PublishedItems from './pages/PublishedItems.vue';
|
||||||
|
import Item from './pages/Item.vue';
|
||||||
|
import Seller from './pages/Seller.vue';
|
||||||
|
import Publish from './pages/Publish.vue';
|
||||||
|
import Buying from './pages/Buying.vue';
|
||||||
|
import Selling from './pages/Selling.vue';
|
||||||
|
import Messages from './pages/Messages.vue';
|
||||||
|
import Profile from './pages/Profile.vue';
|
||||||
|
import NotFound from './pages/NotFound.vue';
|
||||||
|
|
||||||
|
const route_map = {
|
||||||
|
'marketplace/home': Home,
|
||||||
|
'marketplace/search/:keyword': Search,
|
||||||
|
'marketplace/category/:category': Category,
|
||||||
|
'marketplace/item/:item': Item,
|
||||||
|
'marketplace/seller/:seller': Seller,
|
||||||
|
'marketplace/not-found': NotFound,
|
||||||
|
|
||||||
|
// Registered seller routes
|
||||||
|
'marketplace/profile': Profile,
|
||||||
|
'marketplace/saved-items': SavedItems,
|
||||||
|
'marketplace/publish': Publish,
|
||||||
|
'marketplace/published-items': PublishedItems,
|
||||||
|
'marketplace/buying': Buying,
|
||||||
|
'marketplace/buying/:item': Messages,
|
||||||
|
'marketplace/selling': Selling,
|
||||||
|
'marketplace/selling/:buyer/:item': Messages
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current_page: this.get_current_page()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
frappe.route.on('change', () => {
|
||||||
|
this.set_current_page();
|
||||||
|
frappe.utils.scroll_to(0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
set_current_page() {
|
||||||
|
this.current_page = this.get_current_page();
|
||||||
|
},
|
||||||
|
get_current_page() {
|
||||||
|
const curr_route = frappe.get_route_str();
|
||||||
|
let route = Object.keys(route_map).filter(route => route == curr_route)[0];
|
||||||
|
if (!route) {
|
||||||
|
// find route by matching it with dynamic part
|
||||||
|
const curr_route_parts = curr_route.split('/');
|
||||||
|
const weighted_routes = Object.keys(route_map)
|
||||||
|
.map(route_str => route_str.split('/'))
|
||||||
|
.filter(route_parts => route_parts.length === curr_route_parts.length)
|
||||||
|
.reduce((obj, route_parts) => {
|
||||||
|
const key = route_parts.join('/');
|
||||||
|
let weight = 0;
|
||||||
|
route_parts.forEach((part, i) => {
|
||||||
|
const curr_route_part = curr_route_parts[i];
|
||||||
|
if (part === curr_route_part || part.includes(':')) {
|
||||||
|
weight += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
obj[key] = weight;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// get the route with the highest weight
|
||||||
|
for (let key in weighted_routes) {
|
||||||
|
const route_weight = weighted_routes[key];
|
||||||
|
if (route_weight === curr_route_parts.length) {
|
||||||
|
route = key;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
route = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!route) {
|
||||||
|
return NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return route_map[route];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
105
erpnext/public/js/hub/Sidebar.vue
Normal file
105
erpnext/public/js/hub/Sidebar.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="sidebar-container">
|
||||||
|
<ul class="list-unstyled hub-sidebar-group" data-nav-buttons>
|
||||||
|
<li class="hub-sidebar-item" v-for="item in items" :key="item.label" v-route="item.route" v-show="item.condition === undefined || item.condition()">
|
||||||
|
{{ item.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="list-unstyled hub-sidebar-group" data-categories>
|
||||||
|
<li class="hub-sidebar-item is-title bold text-muted">
|
||||||
|
{{ __('Categories') }}
|
||||||
|
</li>
|
||||||
|
<li class="hub-sidebar-item" v-for="category in categories" :key="category.label" v-route="category.route">
|
||||||
|
{{ category.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hub_registered: hub.settings.registered && frappe.session.user === hub.settings.company_email,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: __('Browse'),
|
||||||
|
route: 'marketplace/home'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Saved Items'),
|
||||||
|
route: 'marketplace/saved-items',
|
||||||
|
condition: () => this.hub_registered
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Your Profile'),
|
||||||
|
route: 'marketplace/profile',
|
||||||
|
condition: () => this.hub_registered
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Your Items'),
|
||||||
|
route: 'marketplace/published-items',
|
||||||
|
condition: () => this.hub_registered
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Publish Items'),
|
||||||
|
route: 'marketplace/publish',
|
||||||
|
condition: () => this.hub_registered
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Selling'),
|
||||||
|
route: 'marketplace/selling',
|
||||||
|
condition: () => this.hub_registered
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Buying'),
|
||||||
|
route: 'marketplace/buying',
|
||||||
|
condition: () => this.hub_registered
|
||||||
|
},
|
||||||
|
],
|
||||||
|
categories: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_categories()
|
||||||
|
.then(categories => {
|
||||||
|
this.categories = categories.map(c => {
|
||||||
|
return {
|
||||||
|
label: __(c.name),
|
||||||
|
route: 'marketplace/category/' + c.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.categories.unshift({
|
||||||
|
label: __('All'),
|
||||||
|
route: 'marketplace/home'
|
||||||
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.update_sidebar_state();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
erpnext.hub.on('seller-registered', () => {
|
||||||
|
this.hub_registered = true;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.update_sidebar_state();
|
||||||
|
frappe.route.on('change', () => this.update_sidebar_state());
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_categories() {
|
||||||
|
return hub.call('get_categories');
|
||||||
|
},
|
||||||
|
update_sidebar_state() {
|
||||||
|
const container = $(this.$refs['sidebar-container']);
|
||||||
|
const route = frappe.get_route();
|
||||||
|
const route_str = route.join('/');
|
||||||
|
const part_route_str = route.slice(0, 2).join('/');
|
||||||
|
const $sidebar_item = container.find(`[data-route="${route_str}"], [data-route="${part_route_str}"]`);
|
||||||
|
|
||||||
|
const $siblings = container.find('[data-route]');
|
||||||
|
$siblings.removeClass('active').addClass('text-muted');
|
||||||
|
$sidebar_item.addClass('active').removeClass('text-muted');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
38
erpnext/public/js/hub/components/CommentInput.vue
Normal file
38
erpnext/public/js/hub/components/CommentInput.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div ref="comment-input"></div>
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<span class="text-muted">{{ __('Ctrl + Enter to submit') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="level-right">
|
||||||
|
<button class="btn btn-primary btn-xs" @click="submit_input">{{ __('Submit') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
mounted() {
|
||||||
|
this.make_input();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
make_input() {
|
||||||
|
this.message_input = new frappe.ui.CommentArea({
|
||||||
|
parent: this.$refs['comment-input'],
|
||||||
|
on_submit: (message) => {
|
||||||
|
this.message_input.reset();
|
||||||
|
this.$emit('change', message);
|
||||||
|
},
|
||||||
|
no_wrapper: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submit_input() {
|
||||||
|
if (!this.message_input) return;
|
||||||
|
const value = this.message_input.val();
|
||||||
|
if (!value) return;
|
||||||
|
this.message_input.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
20
erpnext/public/js/hub/components/DetailHeaderItem.vue
Normal file
20
erpnext/public/js/hub/components/DetailHeaderItem.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<p class="text-muted" v-html="header_item"></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const spacer = '<span aria-hidden="true"> · </span>';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'detail-header-item',
|
||||||
|
props: ['value'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
header_item: Array.isArray(this.value)
|
||||||
|
? this.value.join(spacer)
|
||||||
|
: this.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
86
erpnext/public/js/hub/components/DetailView.vue
Normal file
86
erpnext/public/js/hub/components/DetailView.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hub-item-container">
|
||||||
|
<div class="row visible-xs">
|
||||||
|
<div class="col-xs-12 margin-bottom">
|
||||||
|
<button class="btn btn-xs btn-default" data-route="marketplace/home">{{ back_to_home_text }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="show_skeleton" class="row margin-bottom">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="hub-item-skeleton-image"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2 class="hub-skeleton" style="width: 75%;">Name</h2>
|
||||||
|
<div class="text-muted">
|
||||||
|
<p class="hub-skeleton" style="width: 35%;">Details</p>
|
||||||
|
<p class="hub-skeleton" style="width: 50%;">Ratings</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="hub-item-description">
|
||||||
|
<p class="hub-skeleton">Desc</p>
|
||||||
|
<p class="hub-skeleton" style="width: 85%;">Desc</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div class="row margin-bottom">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="hub-item-image">
|
||||||
|
<img v-img-src="image">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
<div class="text-muted">
|
||||||
|
<slot name="detail-header-item"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="menu_items" class="col-md-1">
|
||||||
|
<div class="dropdown pull-right hub-item-dropdown">
|
||||||
|
<a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-right" role="menu">
|
||||||
|
<li v-for="menu_item in menu_items"
|
||||||
|
v-if="menu_item.condition"
|
||||||
|
:key="menu_item.label"
|
||||||
|
>
|
||||||
|
<a @click="menu_item.action">{{ menu_item.label }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-for="section in sections" class="row hub-item-description margin-bottom"
|
||||||
|
:key="section.title"
|
||||||
|
>
|
||||||
|
<h6 class="col-md-12 margin-top">
|
||||||
|
<b class="text-muted">{{ section.title }}</b>
|
||||||
|
</h6>
|
||||||
|
<p class="col-md-12" v-html="section.content">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'detail-view',
|
||||||
|
props: ['title', 'image', 'sections', 'show_skeleton', 'menu_items'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
back_to_home_text: __('Back to Home')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
</style>
|
||||||
50
erpnext/public/js/hub/components/EmptyState.vue
Normal file
50
erpnext/public/js/hub/components/EmptyState.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="empty-state flex flex-column"
|
||||||
|
:class="{ 'bordered': bordered, 'align-center': centered, 'justify-center': centered }"
|
||||||
|
:style="{ height: height + 'px' }"
|
||||||
|
>
|
||||||
|
<p class="text-muted">{{ message }}</p>
|
||||||
|
<p v-if="action">
|
||||||
|
<button class="btn btn-default btn-xs"
|
||||||
|
@click="action.on_click"
|
||||||
|
>
|
||||||
|
{{ action.label }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'empty-state',
|
||||||
|
props: {
|
||||||
|
message: String,
|
||||||
|
bordered: Boolean,
|
||||||
|
height: Number,
|
||||||
|
action: Object,
|
||||||
|
centered: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state.bordered {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-style: dashed;
|
||||||
|
|
||||||
|
// bad, due to item card column layout, that is inner 15px margin
|
||||||
|
margin: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
142
erpnext/public/js/hub/components/ItemCard.vue
Normal file
142
erpnext/public/js/hub/components/ItemCard.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="seen" class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
|
||||||
|
<div class="hub-card"
|
||||||
|
@click="on_click(item_id)"
|
||||||
|
>
|
||||||
|
<div class="hub-card-header flex justify-between">
|
||||||
|
<div class="ellipsis" :style="{ width: '85%' }">
|
||||||
|
<div class="hub-card-title ellipsis bold">{{ title }}</div>
|
||||||
|
<div class="hub-card-subtitle ellipsis text-muted" v-html='subtitle'></div>
|
||||||
|
</div>
|
||||||
|
<i v-if="allow_clear"
|
||||||
|
class="octicon octicon-x text-extra-muted"
|
||||||
|
@click.stop="$emit('remove-item', item_id)"
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div class="hub-card-body">
|
||||||
|
<img class="hub-card-image" v-img-src="item.image"/>
|
||||||
|
<div class="hub-card-overlay">
|
||||||
|
<div v-if="is_local" class="hub-card-overlay-body">
|
||||||
|
<div class="hub-card-overlay-button">
|
||||||
|
<button class="btn btn-default zoom-view">
|
||||||
|
<i class="octicon octicon-pencil text-muted"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'item-card',
|
||||||
|
props: ['item', 'item_id_fieldname', 'is_local', 'on_click', 'allow_clear', 'seen'],
|
||||||
|
computed: {
|
||||||
|
title() {
|
||||||
|
const item_name = this.item.item_name || this.item.name;
|
||||||
|
return strip_html(item_name);
|
||||||
|
},
|
||||||
|
subtitle() {
|
||||||
|
const dot_spacer = '<span aria-hidden="true"> · </span>';
|
||||||
|
if(this.is_local){
|
||||||
|
return comment_when(this.item.creation);
|
||||||
|
} else {
|
||||||
|
let subtitle_items = [comment_when(this.item.creation)];
|
||||||
|
const rating = this.item.average_rating;
|
||||||
|
|
||||||
|
if (rating > 0) {
|
||||||
|
subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitle_items.push(this.item.company);
|
||||||
|
|
||||||
|
return subtitle_items.join(dot_spacer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
item_id() {
|
||||||
|
return this.item[this.item_id_fieldname];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import "../../../../../../frappe/frappe/public/less/variables.less";
|
||||||
|
|
||||||
|
.hub-card {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover .hub-card-overlay {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon-x {
|
||||||
|
display: block;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card.closable {
|
||||||
|
.octicon-x {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card.is-local {
|
||||||
|
&.active {
|
||||||
|
.hub-card-header {
|
||||||
|
background-color: #f4ffe5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-header {
|
||||||
|
position: relative;
|
||||||
|
padding: 12px 15px;
|
||||||
|
height: 60px;
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-body {
|
||||||
|
position: relative;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-overlay {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-overlay-body {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-overlay-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
62
erpnext/public/js/hub/components/ItemCardsContainer.vue
Normal file
62
erpnext/public/js/hub/components/ItemCardsContainer.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="item-cards-container">
|
||||||
|
<empty-state
|
||||||
|
v-if="items.length === 0"
|
||||||
|
:message="empty_state_message"
|
||||||
|
:action="empty_state_action"
|
||||||
|
:bordered="true"
|
||||||
|
:height="empty_state_height"
|
||||||
|
/>
|
||||||
|
<item-card
|
||||||
|
v-for="item in items"
|
||||||
|
:key="container_name + '_' +item[item_id_fieldname]"
|
||||||
|
:item="item"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:is_local="is_local"
|
||||||
|
:on_click="on_click"
|
||||||
|
:allow_clear="editable"
|
||||||
|
:seen="item.hasOwnProperty('seen') ? item.seen : true"
|
||||||
|
@remove-item="$emit('remove-item', item[item_id_fieldname])"
|
||||||
|
>
|
||||||
|
</item-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ItemCard from './ItemCard.vue';
|
||||||
|
import EmptyState from './EmptyState.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'item-cards-container',
|
||||||
|
props: {
|
||||||
|
container_name: String,
|
||||||
|
items: Array,
|
||||||
|
item_id_fieldname: String,
|
||||||
|
is_local: Boolean,
|
||||||
|
on_click: Function,
|
||||||
|
editable: Boolean,
|
||||||
|
|
||||||
|
empty_state_message: String,
|
||||||
|
empty_state_action: Object,
|
||||||
|
empty_state_height: Number,
|
||||||
|
empty_state_bordered: Boolean
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ItemCard,
|
||||||
|
EmptyState
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
items() {
|
||||||
|
// TODO: handling doesn't work
|
||||||
|
frappe.dom.handle_broken_images($(this.$el));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item-cards-container {
|
||||||
|
margin: 0 -15px;
|
||||||
|
overflow: overlay;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
21
erpnext/public/js/hub/components/ItemListCard.vue
Normal file
21
erpnext/public/js/hub/components/ItemListCard.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hub-list-item" :data-route="item.route">
|
||||||
|
<div class="hub-list-left">
|
||||||
|
<img class="hub-list-image" v-img-src="item.image">
|
||||||
|
<div class="hub-list-body ellipsis">
|
||||||
|
<div class="hub-list-title">{{item.item_name}}</div>
|
||||||
|
<div class="hub-list-subtitle ellipsis">
|
||||||
|
<slot name="subtitle"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hub-list-right" v-if="message">
|
||||||
|
<span class="text-muted" v-html="frappe.datetime.comment_when(message.creation, true)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['item', 'message']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
38
erpnext/public/js/hub/components/NotificationMessage.vue
Normal file
38
erpnext/public/js/hub/components/NotificationMessage.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="message" class="subpage-message">
|
||||||
|
<p class="text-muted flex">
|
||||||
|
<span v-html="message"></span>
|
||||||
|
<i class="octicon octicon-x text-extra-muted"
|
||||||
|
@click="$emit('remove-message')"
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notification-message',
|
||||||
|
props: {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.subpage-message {
|
||||||
|
p {
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background-color: #f9fbf7;
|
||||||
|
border-radius: 4px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon-x {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
erpnext/public/js/hub/components/Rating.vue
Normal file
16
erpnext/public/js/hub/components/Rating.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<i v-for="index in max_rating"
|
||||||
|
:key="index"
|
||||||
|
class="fa fa-fw star-icon"
|
||||||
|
:class="{'fa-star': index <= rating, 'fa-star-o': index > rating}"
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['rating', 'max_rating']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
75
erpnext/public/js/hub/components/ReviewArea.vue
Normal file
75
erpnext/public/js/hub/components/ReviewArea.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div ref="review-area" class="timeline-head"></div>
|
||||||
|
<div class="timeline-items">
|
||||||
|
<review-timeline-item v-for="review in reviews"
|
||||||
|
:key="review.user"
|
||||||
|
:username="review.username"
|
||||||
|
:avatar="review.user_image"
|
||||||
|
:comment_when="when(review.modified)"
|
||||||
|
:rating="review.rating"
|
||||||
|
:subject="review.subject"
|
||||||
|
:content="review.content"
|
||||||
|
>
|
||||||
|
</review-timeline-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ReviewTimelineItem from '../components/ReviewTimelineItem.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['hub_item_name'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
reviews: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ReviewTimelineItem
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_item_reviews();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.make_input();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
when(datetime) {
|
||||||
|
return comment_when(datetime);
|
||||||
|
},
|
||||||
|
|
||||||
|
get_item_reviews() {
|
||||||
|
hub.call('get_item_reviews', { hub_item_name: this.hub_item_name })
|
||||||
|
.then(reviews => {
|
||||||
|
this.reviews = reviews;
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
},
|
||||||
|
|
||||||
|
make_input() {
|
||||||
|
this.review_area = new frappe.ui.ReviewArea({
|
||||||
|
parent: this.$refs['review-area'],
|
||||||
|
mentions: [],
|
||||||
|
on_submit: this.on_submit_review.bind(this)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
on_submit_review(values) {
|
||||||
|
values.user = hub.settings.company_email;
|
||||||
|
|
||||||
|
this.review_area.reset();
|
||||||
|
|
||||||
|
hub.call('add_item_review', {
|
||||||
|
hub_item_name: this.hub_item_name,
|
||||||
|
review: JSON.stringify(values)
|
||||||
|
})
|
||||||
|
.then(this.push_review.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
push_review(review){
|
||||||
|
this.reviews.unshift(review);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
54
erpnext/public/js/hub/components/ReviewTimelineItem.vue
Normal file
54
erpnext/public/js/hub/components/ReviewTimelineItem.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
|
||||||
|
<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
|
||||||
|
<!-- ${image_html} -->
|
||||||
|
</span>
|
||||||
|
<div class="pull-left media-body">
|
||||||
|
<div class="media-content-wrapper">
|
||||||
|
<div class="action-btns">
|
||||||
|
<!-- ${edit_html} -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-header clearfix">
|
||||||
|
<span class="pull-left avatar avatar-small visible-xs">
|
||||||
|
<!-- ${image_html} -->
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="asset-details">
|
||||||
|
<span class="author-wrap">
|
||||||
|
<i class="octicon octicon-quote hidden-xs fa-fw"></i>
|
||||||
|
<span>
|
||||||
|
{{ username }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a class="text-muted">
|
||||||
|
<span class="text-muted hidden-xs">–</span>
|
||||||
|
<span class="hidden-xs" v-html="comment_when"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply timeline-content-show">
|
||||||
|
<div class="timeline-item-content">
|
||||||
|
<p class="text-muted">
|
||||||
|
<rating :rating="rating" :max_rating="5"></rating>
|
||||||
|
</p>
|
||||||
|
<h6 class="bold">{{ subject }}</h6>
|
||||||
|
<p class="text-muted" v-html="content"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Rating from '../components/Rating.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['username', 'comment_when', 'avatar', 'rating', 'subject', 'content'],
|
||||||
|
components: {
|
||||||
|
Rating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
26
erpnext/public/js/hub/components/SearchInput.vue
Normal file
26
erpnext/public/js/hub/components/SearchInput.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hub-search-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:value="value"
|
||||||
|
@keydown.enter="on_input">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
placeholder: String,
|
||||||
|
value: String,
|
||||||
|
on_search: Function
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
on_input(event) {
|
||||||
|
this.$emit('input', event.target.value);
|
||||||
|
this.on_search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
3
erpnext/public/js/hub/components/SectionHeader.vue
Normal file
3
erpnext/public/js/hub/components/SectionHeader.vue
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hub-items-header level"><slot></slot></div>
|
||||||
|
</template>
|
||||||
9
erpnext/public/js/hub/components/TimelineItem.vue
Normal file
9
erpnext/public/js/hub/components/TimelineItem.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* Saving this for later */
|
||||||
|
<template>
|
||||||
|
<div class="media timeline-item notification-content">
|
||||||
|
<div class="small">
|
||||||
|
<i class="octicon octicon-bookmark fa-fw"></i>
|
||||||
|
<span title="Administrator"><b>4 weeks ago</b> Published 1 item to Marketplace</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
138
erpnext/public/js/hub/components/detail_view.js
Normal file
138
erpnext/public/js/hub/components/detail_view.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { get_rating_html } from './reviews';
|
||||||
|
|
||||||
|
function get_detail_view_html(item, allow_edit) {
|
||||||
|
const title = item.item_name || item.name;
|
||||||
|
const seller = item.company;
|
||||||
|
|
||||||
|
const who = __('Posted By {0}', [seller]);
|
||||||
|
const when = comment_when(item.creation);
|
||||||
|
|
||||||
|
const city = item.city ? item.city + ', ' : '';
|
||||||
|
const country = item.country ? item.country : '';
|
||||||
|
const where = `${city}${country}`;
|
||||||
|
|
||||||
|
const dot_spacer = '<span aria-hidden="true"> · </span>';
|
||||||
|
|
||||||
|
const description = item.description || '';
|
||||||
|
|
||||||
|
let stats = __('No views yet');
|
||||||
|
if(item.view_count) {
|
||||||
|
const views_message = __(`${item.view_count} Views`);
|
||||||
|
|
||||||
|
const rating_html = get_rating_html(item.average_rating);
|
||||||
|
const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet');
|
||||||
|
|
||||||
|
stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let favourite_button = ''
|
||||||
|
if (hub.settings.registered) {
|
||||||
|
favourite_button = !item.favourited
|
||||||
|
? `<button class="btn btn-default text-muted favourite-button" data-action="add_to_favourites">
|
||||||
|
${__('Save')} <i class="octicon octicon-heart text-extra-muted"></i>
|
||||||
|
</button>`
|
||||||
|
: `<button class="btn btn-default text-muted favourite-button disabled" data-action="add_to_favourites">
|
||||||
|
${__('Saved')}
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contact_seller_button = item.hub_seller !== hub.settings.company_email
|
||||||
|
? `<button class="btn btn-primary" data-action="contact_seller">
|
||||||
|
${__('Contact Seller')}
|
||||||
|
</button>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
let menu_items = '';
|
||||||
|
|
||||||
|
if(allow_edit) {
|
||||||
|
menu_items = `
|
||||||
|
<li><a data-action="edit_details">${__('Edit Details')}</a></li>
|
||||||
|
<li><a data-action="unpublish_item">${__('Unpublish')}</a></li>`;
|
||||||
|
} else {
|
||||||
|
menu_items = `
|
||||||
|
<li><a data-action="report_item">${__('Report this item')}</a></li>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<div class="hub-item-container">
|
||||||
|
<div class="row visible-xs">
|
||||||
|
<div class="col-xs-12 margin-bottom">
|
||||||
|
<button class="btn btn-xs btn-default" data-route="marketplace/home">${__('Back to home')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row detail-page-section margin-bottom">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="hub-item-image">
|
||||||
|
<img src="${item.image}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 flex flex-column">
|
||||||
|
<div class="detail-page-header">
|
||||||
|
<h2>${title}</h2>
|
||||||
|
<div class="text-muted">
|
||||||
|
<p>${where}${dot_spacer}${when}</p>
|
||||||
|
<p>${stats}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-actions detail-page-actions">
|
||||||
|
${favourite_button}
|
||||||
|
${contact_seller_button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<div class="dropdown pull-right hub-item-dropdown">
|
||||||
|
<a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-right" role="menu">
|
||||||
|
${menu_items}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row hub-item-description">
|
||||||
|
<h6 class="col-md-12 margin-top">
|
||||||
|
<b class="text-muted">Item Description</b>
|
||||||
|
</h6>
|
||||||
|
<p class="col-md-12">
|
||||||
|
${description ? description : __('No details')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="row hub-item-seller">
|
||||||
|
|
||||||
|
<h6 class="col-md-12 margin-top margin-bottom">
|
||||||
|
<b class="text-muted">Seller Information</b>
|
||||||
|
</h6>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<img src="https://picsum.photos/200">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="margin-bottom"><a href="#marketplace/seller/${seller}" class="bold">${seller}</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- review area -->
|
||||||
|
<div class="row hub-item-review-container">
|
||||||
|
<div class="col-md-12 form-footer">
|
||||||
|
<div class="form-comments">
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="timeline-head"></div>
|
||||||
|
<div class="timeline-items"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right scroll-to-top">
|
||||||
|
<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
get_detail_view_html,
|
||||||
|
get_profile_html
|
||||||
|
}
|
||||||
80
erpnext/public/js/hub/components/item_card.js
Normal file
80
erpnext/public/js/hub/components/item_card.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
function get_buying_item_message_card_html(item) {
|
||||||
|
const item_name = item.item_name || item.name;
|
||||||
|
const title = strip_html(item_name);
|
||||||
|
|
||||||
|
const message = item.recent_message
|
||||||
|
const sender = message.sender === frappe.session.user ? 'You' : message.sender
|
||||||
|
const content = strip_html(message.content)
|
||||||
|
|
||||||
|
// route
|
||||||
|
item.route = `marketplace/buying/${item.name}`
|
||||||
|
|
||||||
|
const item_html = `
|
||||||
|
<div class="col-md-7">
|
||||||
|
<div class="hub-list-item" data-route="${item.route}">
|
||||||
|
<div class="hub-list-left">
|
||||||
|
<img class="hub-list-image" src="${item.image}">
|
||||||
|
<div class="hub-list-body ellipsis">
|
||||||
|
<div class="hub-list-title">${item_name}</div>
|
||||||
|
<div class="hub-list-subtitle ellipsis">
|
||||||
|
<span>${sender}: </span>
|
||||||
|
<span>${content}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hub-list-right">
|
||||||
|
<span class="text-muted">${comment_when(message.creation, true)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return item_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_selling_item_message_card_html(item) {
|
||||||
|
const item_name = item.item_name || item.name;
|
||||||
|
const title = strip_html(item_name);
|
||||||
|
|
||||||
|
// route
|
||||||
|
if (!item.route) {
|
||||||
|
item.route = `marketplace/item/${item.name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let received_messages = '';
|
||||||
|
item.received_messages.forEach(message => {
|
||||||
|
const sender = message.sender === frappe.session.user ? 'You' : message.sender
|
||||||
|
const content = strip_html(message.content)
|
||||||
|
|
||||||
|
received_messages += `
|
||||||
|
<div class="received-message">
|
||||||
|
<span class="text-muted">${comment_when(message.creation, true)}</span>
|
||||||
|
<div class="ellipsis">
|
||||||
|
<span class="bold">${sender}: </span>
|
||||||
|
<span>${content}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
const item_html = `
|
||||||
|
<div class="selling-item-message-card">
|
||||||
|
<div class="selling-item-detail" data-route="${item.route}">
|
||||||
|
<img class="item-image" src="${item.image}">
|
||||||
|
<h5 class="item-name">${item_name}</h5>
|
||||||
|
<div class="received-message-container">
|
||||||
|
${received_messages}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return item_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
get_item_card_html,
|
||||||
|
get_local_item_card_html,
|
||||||
|
get_buying_item_message_card_html,
|
||||||
|
get_selling_item_message_card_html
|
||||||
|
}
|
||||||
42
erpnext/public/js/hub/components/item_publish_dialog.js
Normal file
42
erpnext/public/js/hub/components/item_publish_dialog.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
function ItemPublishDialog(primary_action, secondary_action) {
|
||||||
|
let dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Edit Publishing Details'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
"label": "Item Code",
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Hub Category",
|
||||||
|
"fieldname": "hub_category",
|
||||||
|
"fieldtype": "Autocomplete",
|
||||||
|
"options": [],
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Images",
|
||||||
|
"fieldname": "image_list",
|
||||||
|
"fieldtype": "MultiSelect",
|
||||||
|
"options": [],
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action_label: primary_action.label || __('Set Details'),
|
||||||
|
primary_action: primary_action.fn,
|
||||||
|
secondary_action: secondary_action.fn
|
||||||
|
});
|
||||||
|
|
||||||
|
hub.call('get_categories')
|
||||||
|
.then(categories => {
|
||||||
|
categories = categories.map(d => d.name);
|
||||||
|
dialog.fields_dict.hub_category.set_data(categories);
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ItemPublishDialog
|
||||||
|
}
|
||||||
77
erpnext/public/js/hub/components/profile_dialog.js
Normal file
77
erpnext/public/js/hub/components/profile_dialog.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
const ProfileDialog = (title = __('Edit Profile'), action={}, initial_values={}) => {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
fieldtype: 'Link',
|
||||||
|
fieldname: 'company',
|
||||||
|
label: __('Company'),
|
||||||
|
options: 'Company',
|
||||||
|
onchange: () => {
|
||||||
|
const value = dialog.get_value('company');
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
frappe.db.get_doc('Company', value)
|
||||||
|
.then(company => {
|
||||||
|
dialog.set_values({
|
||||||
|
country: company.country,
|
||||||
|
company_email: company.email,
|
||||||
|
currency: company.default_currency
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'company_email',
|
||||||
|
label: __('Email'),
|
||||||
|
fieldtype: 'Data'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'country',
|
||||||
|
label: __('Country'),
|
||||||
|
fieldtype: 'Read Only'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'currency',
|
||||||
|
label: __('Currency'),
|
||||||
|
fieldtype: 'Read Only'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Text',
|
||||||
|
label: __('About your Company'),
|
||||||
|
fieldname: 'company_description'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let dialog = new frappe.ui.Dialog({
|
||||||
|
title: title,
|
||||||
|
fields: fields,
|
||||||
|
primary_action_label: action.label || __('Update'),
|
||||||
|
primary_action: () => {
|
||||||
|
const form_values = dialog.get_values();
|
||||||
|
let values_filled = true;
|
||||||
|
const mandatory_fields = ['company', 'company_email', 'company_description'];
|
||||||
|
mandatory_fields.forEach(field => {
|
||||||
|
const value = form_values[field];
|
||||||
|
if (!value) {
|
||||||
|
dialog.set_df_property(field, 'reqd', 1);
|
||||||
|
values_filled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!values_filled) return;
|
||||||
|
|
||||||
|
action.on_submit(form_values);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.set_values(initial_values);
|
||||||
|
|
||||||
|
// Post create
|
||||||
|
const default_company = frappe.defaults.get_default('company');
|
||||||
|
dialog.set_value('company', default_company);
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ProfileDialog
|
||||||
|
}
|
||||||
80
erpnext/public/js/hub/components/reviews.js
Normal file
80
erpnext/public/js/hub/components/reviews.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
function get_review_html(review) {
|
||||||
|
let username = review.username || review.user || __("Anonymous");
|
||||||
|
|
||||||
|
let image_html = review.user_image
|
||||||
|
? `<div class="avatar-frame" style="background-image: url(${review.user_image})"></div>`
|
||||||
|
: `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
|
||||||
|
|
||||||
|
let edit_html = review.own
|
||||||
|
? `<div class="pull-right hidden-xs close-btn-container">
|
||||||
|
<span class="small text-muted">
|
||||||
|
${'data.delete'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right edit-btn-container">
|
||||||
|
<span class="small text-muted">
|
||||||
|
${'data.edit'}
|
||||||
|
</span>
|
||||||
|
</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
let rating_html = get_rating_html(review.rating);
|
||||||
|
|
||||||
|
return get_timeline_item(review, image_html, edit_html, rating_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_timeline_item(data, image_html, edit_html, rating_html) {
|
||||||
|
return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
|
||||||
|
<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
|
||||||
|
${image_html}
|
||||||
|
</span>
|
||||||
|
<div class="pull-left media-body">
|
||||||
|
<div class="media-content-wrapper">
|
||||||
|
<div class="action-btns">${edit_html}</div>
|
||||||
|
|
||||||
|
<div class="comment-header clearfix">
|
||||||
|
<span class="pull-left avatar avatar-small visible-xs">
|
||||||
|
${image_html}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="asset-details">
|
||||||
|
<span class="author-wrap">
|
||||||
|
<i class="octicon octicon-quote hidden-xs fa-fw"></i>
|
||||||
|
<span>${data.username}</span>
|
||||||
|
</span>
|
||||||
|
<a class="text-muted">
|
||||||
|
<span class="text-muted hidden-xs">–</span>
|
||||||
|
<span class="hidden-xs">${comment_when(data.modified)}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply timeline-content-show">
|
||||||
|
<div class="timeline-item-content">
|
||||||
|
<p class="text-muted">
|
||||||
|
${rating_html}
|
||||||
|
</p>
|
||||||
|
<h6 class="bold">${data.subject}</h6>
|
||||||
|
<p class="text-muted">
|
||||||
|
${data.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_rating_html(rating) {
|
||||||
|
let rating_html = ``;
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
let star_class = 'fa-star';
|
||||||
|
if (i >= rating) star_class = 'fa-star-o';
|
||||||
|
rating_html += `<i class='fa fa-fw ${star_class} star-icon' data-index=${i}></i>`;
|
||||||
|
}
|
||||||
|
return rating_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
get_review_html,
|
||||||
|
get_rating_html
|
||||||
|
}
|
||||||
31
erpnext/public/js/hub/event_emitter.js
Normal file
31
erpnext/public/js/hub/event_emitter.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Simple EventEmitter which uses jQuery's event system
|
||||||
|
*/
|
||||||
|
class EventEmitter {
|
||||||
|
init() {
|
||||||
|
this.jq = jQuery(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger(evt, data) {
|
||||||
|
!this.jq && this.init();
|
||||||
|
this.jq.trigger(evt, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
once(evt, handler) {
|
||||||
|
!this.jq && this.init();
|
||||||
|
this.jq.one(evt, (e, data) => handler(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
on(evt, handler) {
|
||||||
|
!this.jq && this.init();
|
||||||
|
this.jq.bind(evt, (e, data) => handler(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
off(evt, handler) {
|
||||||
|
!this.jq && this.init();
|
||||||
|
this.jq.unbind(evt, (e, data) => handler(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default EventEmitter;
|
||||||
58
erpnext/public/js/hub/hub_call.js
Normal file
58
erpnext/public/js/hub/hub_call.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
frappe.provide('hub');
|
||||||
|
frappe.provide('erpnext.hub');
|
||||||
|
|
||||||
|
erpnext.hub.cache = {};
|
||||||
|
hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { // eslint-disable-line
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
// cache
|
||||||
|
const key = method + JSON.stringify(args);
|
||||||
|
if (erpnext.hub.cache[key]) {
|
||||||
|
resolve(erpnext.hub.cache[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache invalidation
|
||||||
|
const clear_cache = () => delete erpnext.hub.cache[key];
|
||||||
|
|
||||||
|
if (!clear_cache_on_event) {
|
||||||
|
invalidate_after_5_mins(clear_cache);
|
||||||
|
} else {
|
||||||
|
erpnext.hub.on(clear_cache_on_event, () => {
|
||||||
|
clear_cache(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.hub_node.api.call_hub_method',
|
||||||
|
args: {
|
||||||
|
method,
|
||||||
|
params: args
|
||||||
|
}
|
||||||
|
}).then(r => {
|
||||||
|
if (r.message) {
|
||||||
|
const response = r.message;
|
||||||
|
if (response.error) {
|
||||||
|
frappe.throw({
|
||||||
|
title: __('Marketplace Error'),
|
||||||
|
message: response.error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
erpnext.hub.cache[key] = response;
|
||||||
|
erpnext.hub.trigger(`response:${key}`, { response });
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
reject(r);
|
||||||
|
|
||||||
|
}).fail(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function invalidate_after_5_mins(clear_cache) {
|
||||||
|
// cache invalidation after 5 minutes
|
||||||
|
const timeout = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
clear_cache();
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
@@ -1,80 +1,32 @@
|
|||||||
frappe.provide('erpnext.hub.pages');
|
frappe.provide('erpnext.hub');
|
||||||
|
|
||||||
frappe.views.HubFactory = class HubFactory extends frappe.views.Factory {
|
frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory {
|
||||||
make(route) {
|
show() {
|
||||||
const page_name = frappe.get_route_str();
|
if (frappe.pages.marketplace) {
|
||||||
const page = route[1];
|
frappe.container.change_to('marketplace');
|
||||||
|
erpnext.hub.marketplace.refresh();
|
||||||
|
} else {
|
||||||
|
this.make('marketplace');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const assets = {
|
make(page_name) {
|
||||||
'List': [
|
const assets = [
|
||||||
'/assets/erpnext/js/hub/hub_listing.js',
|
'/assets/js/marketplace.min.js'
|
||||||
],
|
];
|
||||||
'Form': [
|
|
||||||
'/assets/erpnext/js/hub/hub_form.js'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
frappe.model.with_doc('Hub Settings', 'Hub Settings', () => {
|
|
||||||
this.hub_settings = frappe.get_doc('Hub Settings');
|
|
||||||
|
|
||||||
if (!erpnext.hub.pages[page_name]) {
|
frappe.require(assets, () => {
|
||||||
if(!frappe.is_online()) {
|
erpnext.hub.marketplace = new erpnext.hub.Marketplace({
|
||||||
this.render_offline_card();
|
parent: this.make_page(true, page_name)
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
if (!route[2]) {
|
|
||||||
frappe.require(assets['List'], () => {
|
|
||||||
if(page === 'Favourites') {
|
|
||||||
erpnext.hub.pages[page_name] = new erpnext.hub['Favourites']({
|
|
||||||
parent: this.make_page(true, page_name),
|
|
||||||
hub_settings: this.hub_settings
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
erpnext.hub.pages[page_name] = new erpnext.hub[page+'Listing']({
|
|
||||||
parent: this.make_page(true, page_name),
|
|
||||||
hub_settings: this.hub_settings
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!route[3]){
|
|
||||||
frappe.require(assets['Form'], () => {
|
|
||||||
erpnext.hub.pages[page_name] = new erpnext.hub[page+'Page']({
|
|
||||||
unique_id: route[2],
|
|
||||||
doctype: route[2],
|
|
||||||
parent: this.make_page(true, page_name),
|
|
||||||
hub_settings: this.hub_settings
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
frappe.require(assets['List'], () => {
|
|
||||||
frappe.route_options = {};
|
|
||||||
frappe.route_options["company_name"] = route[2]
|
|
||||||
erpnext.hub.pages[page_name] = new erpnext.hub['ItemListing']({
|
|
||||||
parent: this.make_page(true, page_name),
|
|
||||||
hub_settings: this.hub_settings
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
window.hub_page = erpnext.hub.pages[page_name];
|
|
||||||
} else {
|
|
||||||
frappe.container.change_to(page_name);
|
|
||||||
window.hub_page = erpnext.hub.pages[page_name];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render_offline_card() {
|
|
||||||
let html = `<div class='page-card' style='margin: 140px auto;'>
|
|
||||||
<div class='page-card-head'>
|
|
||||||
<span class='indicator red'>${'Failed to connect'}</span>
|
|
||||||
</div>
|
|
||||||
<p>${ __("Please check your network connection.") }</p>
|
|
||||||
<div><a href='#Hub/Item' class='btn btn-primary btn-sm'>
|
|
||||||
${ __("Reload") }</a></div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let page = $('#body_div');
|
|
||||||
page.append(html);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$(document).on('toolbar_setup', () => {
|
||||||
|
$('#toolbar-user .navbar-reload').after(`
|
||||||
|
<li>
|
||||||
|
<a href="#marketplace/home">${__('Marketplace')}
|
||||||
|
</li>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,493 +0,0 @@
|
|||||||
frappe.provide('erpnext.hub');
|
|
||||||
|
|
||||||
erpnext.hub.HubDetailsPage = class HubDetailsPage extends frappe.views.BaseList {
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.method = 'erpnext.hub_node.get_details';
|
|
||||||
const route = frappe.get_route();
|
|
||||||
// this.page_name = route[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_fields() {
|
|
||||||
return this.get_meta()
|
|
||||||
.then(r => {
|
|
||||||
this.meta = r.message.meta || this.meta;
|
|
||||||
this.categories = r.message.categories || [];
|
|
||||||
this.bootstrap_data(r.message);
|
|
||||||
|
|
||||||
this.getFormFields();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrap_data() { }
|
|
||||||
|
|
||||||
get_meta() {
|
|
||||||
return new Promise(resolve =>
|
|
||||||
frappe.call('erpnext.hub_node.get_meta', {doctype: 'Hub ' + this.doctype}, resolve));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
set_breadcrumbs() {
|
|
||||||
frappe.breadcrumbs.add({
|
|
||||||
label: __('Hub'),
|
|
||||||
route: '#Hub/' + this.doctype,
|
|
||||||
type: 'Custom'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_side_bar() {
|
|
||||||
this.sidebar = new frappe.ui.Sidebar({
|
|
||||||
wrapper: this.$page.find('.layout-side-section'),
|
|
||||||
css_class: 'hub-form-sidebar'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_filter_area() { }
|
|
||||||
|
|
||||||
setup_sort_selector() { }
|
|
||||||
|
|
||||||
// let category = this.quick_view.get_values().hub_category;
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// frappe.call({
|
|
||||||
// method: 'erpnext.hub_node.update_category',
|
|
||||||
// args: {
|
|
||||||
// hub_item_code: values.hub_item_code,
|
|
||||||
// category: category,
|
|
||||||
// },
|
|
||||||
// callback: (r) => {
|
|
||||||
// resolve();
|
|
||||||
// },
|
|
||||||
// freeze: true
|
|
||||||
// }).fail(reject);
|
|
||||||
// });
|
|
||||||
|
|
||||||
get_timeline() {
|
|
||||||
return `<div class="timeline">
|
|
||||||
<div class="timeline-head">
|
|
||||||
</div>
|
|
||||||
<div class="timeline-new-email">
|
|
||||||
<button class="btn btn-default btn-reply-email btn-xs">
|
|
||||||
${__("Reply")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-items"></div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_footer() {
|
|
||||||
return `<div class="form-footer">
|
|
||||||
<div class="after-save">
|
|
||||||
<div class="form-comments"></div>
|
|
||||||
</div>
|
|
||||||
<div class="pull-right scroll-to-top">
|
|
||||||
<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_args() {
|
|
||||||
return {
|
|
||||||
hub_sync_id: this.unique_id,
|
|
||||||
doctype: 'Hub ' + this.doctype
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
prepare_data(r) {
|
|
||||||
this.data = r.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_data(r) {
|
|
||||||
this.data = r.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const image_html = this.data[this.image_field_name] ?
|
|
||||||
`<img src="${this.data[this.image_field_name]}">
|
|
||||||
<span class="helper"></span>` :
|
|
||||||
`<div class="standard-image">${frappe.get_abbr(this.page_title)}</div>`;
|
|
||||||
|
|
||||||
this.sidebar.remove_item('image');
|
|
||||||
this.sidebar.add_item({
|
|
||||||
name: 'image',
|
|
||||||
label: image_html
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!this.form) {
|
|
||||||
let fields = this.formFields;
|
|
||||||
this.form = new frappe.ui.FieldGroup({
|
|
||||||
parent: this.$result,
|
|
||||||
fields
|
|
||||||
});
|
|
||||||
this.form.make();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.data.hub_category) {
|
|
||||||
this.form.fields_dict.set_category.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.form.set_values(this.data);
|
|
||||||
this.$result.show();
|
|
||||||
|
|
||||||
this.$timelineList && this.$timelineList.empty();
|
|
||||||
if(this.data.reviews && this.data.reviews.length) {
|
|
||||||
this.data.reviews.map(review => {
|
|
||||||
this.addReviewToTimeline(review);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.postRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
postRender() {}
|
|
||||||
|
|
||||||
attachFooter() {
|
|
||||||
let footerHtml = `<div class="form-footer">
|
|
||||||
<div class="form-comments"></div>
|
|
||||||
<div class="pull-right scroll-to-top">
|
|
||||||
<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let parent = $('<div>').appendTo(this.page.main.parent());
|
|
||||||
this.$footer = $(footerHtml).appendTo(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachTimeline() {
|
|
||||||
let timelineHtml = `<div class="timeline">
|
|
||||||
<div class="timeline-head">
|
|
||||||
</div>
|
|
||||||
<div class="timeline-new-email">
|
|
||||||
<button class="btn btn-default btn-reply-email btn-xs">
|
|
||||||
${ __("Reply") }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-items"></div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let parent = this.$footer.find(".form-comments");
|
|
||||||
this.$timeline = $(timelineHtml).appendTo(parent);
|
|
||||||
|
|
||||||
this.$timelineList = this.$timeline.find(".timeline-items");
|
|
||||||
}
|
|
||||||
|
|
||||||
attachReviewArea() {
|
|
||||||
this.comment_area = new frappe.ui.ReviewArea({
|
|
||||||
parent: this.$footer.find('.timeline-head'),
|
|
||||||
mentions: [],
|
|
||||||
on_submit: (val) => {
|
|
||||||
val.user = frappe.session.user;
|
|
||||||
val.username = frappe.session.user_fullname;
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hub_node.send_review',
|
|
||||||
args: {
|
|
||||||
hub_item_code: this.data.hub_item_code,
|
|
||||||
review: val
|
|
||||||
},
|
|
||||||
callback: (r) => {
|
|
||||||
this.refresh();
|
|
||||||
this.comment_area.reset();
|
|
||||||
},
|
|
||||||
freeze: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addReviewToTimeline(data) {
|
|
||||||
let username = data.username || data.user || __("Anonymous")
|
|
||||||
let imageHtml = data.user_image
|
|
||||||
? `<div class="avatar-frame" style="background-image: url(${data.user_image})"></div>`
|
|
||||||
: `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
|
|
||||||
|
|
||||||
let editHtml = data.own
|
|
||||||
? `<div class="pull-right hidden-xs close-btn-container">
|
|
||||||
<span class="small text-muted">
|
|
||||||
${'data.delete'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="pull-right edit-btn-container">
|
|
||||||
<span class="small text-muted">
|
|
||||||
${'data.edit'}
|
|
||||||
</span>
|
|
||||||
</div>`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
let ratingHtml = '';
|
|
||||||
|
|
||||||
for(var i = 0; i < 5; i++) {
|
|
||||||
let starIcon = 'fa-star-o'
|
|
||||||
if(i < data.rating) {
|
|
||||||
starIcon = 'fa-star';
|
|
||||||
}
|
|
||||||
ratingHtml += `<i class="fa fa-fw ${starIcon} star-icon" data-idx='${i}'></i>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this.getTimelineItem(data, imageHtml, editHtml, ratingHtml))
|
|
||||||
.appendTo(this.$timelineList);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTimelineItem(data, imageHtml, editHtml, ratingHtml) {
|
|
||||||
return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
|
|
||||||
<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
|
|
||||||
${imageHtml}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="pull-left media-body">
|
|
||||||
<div class="media-content-wrapper">
|
|
||||||
<div class="action-btns">${editHtml}</div>
|
|
||||||
|
|
||||||
<div class="comment-header clearfix small ${'linksActive'}">
|
|
||||||
<span class="pull-left avatar avatar-small visible-xs">
|
|
||||||
${imageHtml}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="asset-details">
|
|
||||||
<span class="author-wrap">
|
|
||||||
<i class="octicon octicon-quote hidden-xs fa-fw"></i>
|
|
||||||
<span>${data.username}</span>
|
|
||||||
</span>
|
|
||||||
<a href="#Form/${''}" class="text-muted">
|
|
||||||
<span class="text-muted hidden-xs">–</span>
|
|
||||||
<span class="indicator-right ${'green'}
|
|
||||||
delivery-status-indicator">
|
|
||||||
<span class="hidden-xs">${data.pretty_date}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="text-muted reply-link pull-right timeline-content-show"
|
|
||||||
title="${__('Reply')}"> ${''} </a>
|
|
||||||
<span class="comment-likes hidden-xs">
|
|
||||||
<i class="octicon octicon-heart like-action text-extra-muted not-liked fa-fw">
|
|
||||||
</i>
|
|
||||||
<span class="likes-count text-muted">10</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="reply timeline-content-show">
|
|
||||||
<div class="timeline-item-content">
|
|
||||||
<p class="text-muted small">
|
|
||||||
<b>${data.subject}</b>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p class="text-muted small">
|
|
||||||
${ratingHtml}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<p>
|
|
||||||
${data.content}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareFormFields(fields, fieldnames) {
|
|
||||||
return fields
|
|
||||||
.filter(field => fieldnames.includes(field.fieldname))
|
|
||||||
.map(field => {
|
|
||||||
let {
|
|
||||||
label,
|
|
||||||
fieldname,
|
|
||||||
fieldtype,
|
|
||||||
} = field;
|
|
||||||
let read_only = 1;
|
|
||||||
return {
|
|
||||||
label,
|
|
||||||
fieldname,
|
|
||||||
fieldtype,
|
|
||||||
read_only,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubDetailsPage {
|
|
||||||
constructor(opts) {
|
|
||||||
super(opts);
|
|
||||||
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.doctype = 'Item';
|
|
||||||
this.image_field_name = 'image';
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_page_head() {
|
|
||||||
super.setup_page_head();
|
|
||||||
this.set_primary_action();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_side_bar() {
|
|
||||||
super.setup_side_bar();
|
|
||||||
this.attachFooter();
|
|
||||||
this.attachTimeline();
|
|
||||||
this.attachReviewArea();
|
|
||||||
}
|
|
||||||
|
|
||||||
set_primary_action() {
|
|
||||||
let item = this.data;
|
|
||||||
this.page.set_primary_action(__('Request a Quote'), () => {
|
|
||||||
this.show_rfq_modal()
|
|
||||||
.then(values => {
|
|
||||||
item.item_code = values.item_code;
|
|
||||||
delete values.item_code;
|
|
||||||
|
|
||||||
const supplier = values;
|
|
||||||
return [item, supplier];
|
|
||||||
})
|
|
||||||
.then(([item, supplier]) => {
|
|
||||||
return this.make_rfq(item, supplier, this.page.btn_primary);
|
|
||||||
})
|
|
||||||
.then(r => {
|
|
||||||
console.log(r);
|
|
||||||
if (r.message && r.message.rfq) {
|
|
||||||
this.page.btn_primary.addClass('disabled').html(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
|
|
||||||
} else {
|
|
||||||
throw r;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e); //eslint-disable-line
|
|
||||||
});
|
|
||||||
}, 'octicon octicon-plus');
|
|
||||||
}
|
|
||||||
|
|
||||||
prepare_data(r) {
|
|
||||||
super.prepare_data(r);
|
|
||||||
this.page.set_title(this.data["item_name"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
make_rfq(item, supplier, btn) {
|
|
||||||
console.log(supplier);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hub_node.make_rfq_and_send_opportunity',
|
|
||||||
args: { item, supplier },
|
|
||||||
callback: resolve,
|
|
||||||
btn,
|
|
||||||
}).fail(reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
postRender() {
|
|
||||||
this.categoryDialog = new frappe.ui.Dialog({
|
|
||||||
title: __('Suggest Category'),
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: __('Category'),
|
|
||||||
fieldname: 'category',
|
|
||||||
fieldtype: 'Autocomplete',
|
|
||||||
options: this.categories,
|
|
||||||
reqd: 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
primary_action_label: __("Send"),
|
|
||||||
primary_action: () => {
|
|
||||||
let values = this.categoryDialog.get_values();
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hub_node.update_category',
|
|
||||||
args: {
|
|
||||||
hub_item_code: this.data.hub_item_code,
|
|
||||||
category: values.category
|
|
||||||
},
|
|
||||||
callback: () => {
|
|
||||||
this.categoryDialog.hide();
|
|
||||||
this.refresh();
|
|
||||||
},
|
|
||||||
freeze: true
|
|
||||||
}).fail(() => {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getFormFields() {
|
|
||||||
let colOneFieldnames = ['item_name', 'item_code', 'description'];
|
|
||||||
let colTwoFieldnames = ['seller', 'company_name', 'country'];
|
|
||||||
let colOneFields = this.prepareFormFields(this.meta.fields, colOneFieldnames);
|
|
||||||
let colTwoFields = this.prepareFormFields(this.meta.fields, colTwoFieldnames);
|
|
||||||
|
|
||||||
let miscFields = [
|
|
||||||
{
|
|
||||||
label: __('Category'),
|
|
||||||
fieldname: 'hub_category',
|
|
||||||
fieldtype: 'Data',
|
|
||||||
read_only: 1
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
label: __('Suggest Category?'),
|
|
||||||
fieldname: 'set_category',
|
|
||||||
fieldtype: 'Button',
|
|
||||||
click: () => {
|
|
||||||
this.categoryDialog.show();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
fieldname: 'cb1',
|
|
||||||
fieldtype: 'Column Break'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.formFields = colOneFields.concat(miscFields, colTwoFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
show_rfq_modal() {
|
|
||||||
let item = this.data;
|
|
||||||
return new Promise(res => {
|
|
||||||
let fields = [
|
|
||||||
{ label: __('Item Code'), fieldtype: 'Data', fieldname: 'item_code', default: item.item_code },
|
|
||||||
{ fieldtype: 'Column Break' },
|
|
||||||
{ label: __('Item Group'), fieldtype: 'Link', fieldname: 'item_group', default: item.item_group },
|
|
||||||
{ label: __('Supplier Details'), fieldtype: 'Section Break' },
|
|
||||||
{ label: __('Supplier Name'), fieldtype: 'Data', fieldname: 'supplier_name', default: item.company_name },
|
|
||||||
{ label: __('Supplier Email'), fieldtype: 'Data', fieldname: 'supplier_email', default: item.seller },
|
|
||||||
{ fieldtype: 'Column Break' },
|
|
||||||
{ label: __('Supplier Group'), fieldname: 'supplier_group',
|
|
||||||
fieldtype: 'Link', options: 'Supplier Group' }
|
|
||||||
];
|
|
||||||
fields = fields.map(f => { f.reqd = 1; return f; });
|
|
||||||
|
|
||||||
const d = new frappe.ui.Dialog({
|
|
||||||
title: __('Request for Quotation'),
|
|
||||||
fields: fields,
|
|
||||||
primary_action_label: __('Send'),
|
|
||||||
primary_action: (values) => {
|
|
||||||
res(values);
|
|
||||||
d.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
d.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
erpnext.hub.CompanyPage = class CompanyPage extends erpnext.hub.HubDetailsPage {
|
|
||||||
constructor(opts) {
|
|
||||||
super(opts);
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.doctype = 'Company';
|
|
||||||
this.image_field_name = 'company_logo';
|
|
||||||
}
|
|
||||||
|
|
||||||
prepare_data(r) {
|
|
||||||
super.prepare_data(r);
|
|
||||||
this.page.set_title(this.data["company_name"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFormFields() {
|
|
||||||
let fieldnames = ['company_name', 'description', 'route', 'country', 'seller', 'site_name'];;
|
|
||||||
this.formFields = this.prepareFormFields(this.meta.fields, fieldnames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,718 +0,0 @@
|
|||||||
frappe.provide('erpnext.hub');
|
|
||||||
|
|
||||||
erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList {
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.page_title = __('');
|
|
||||||
this.method = 'erpnext.hub_node.get_list';
|
|
||||||
|
|
||||||
this.cache = {};
|
|
||||||
|
|
||||||
const route = frappe.get_route();
|
|
||||||
this.page_name = route[1];
|
|
||||||
|
|
||||||
this.menu_items = this.menu_items.concat(this.get_menu_items());
|
|
||||||
|
|
||||||
this.imageFieldName = 'image';
|
|
||||||
|
|
||||||
this.show_filters = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_title() {
|
|
||||||
const title = this.page_title;
|
|
||||||
let iconHtml = `<img class="hub-icon" src="assets/erpnext/images/hub_logo.svg">`;
|
|
||||||
let titleHtml = `<span class="hub-page-title">${title}</span>`;
|
|
||||||
this.page.set_title(iconHtml + titleHtml, '', false, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_fields() {
|
|
||||||
return this.get_meta()
|
|
||||||
.then(r => {
|
|
||||||
this.meta = r.message.meta || this.meta;
|
|
||||||
frappe.model.sync(this.meta);
|
|
||||||
this.bootstrap_data(r.message);
|
|
||||||
|
|
||||||
this.prepareFormFields();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get_meta() {
|
|
||||||
return new Promise(resolve =>
|
|
||||||
frappe.call('erpnext.hub_node.get_meta', {doctype: this.doctype}, resolve));
|
|
||||||
}
|
|
||||||
|
|
||||||
set_breadcrumbs() { }
|
|
||||||
|
|
||||||
prepareFormFields() { }
|
|
||||||
|
|
||||||
bootstrap_data() { }
|
|
||||||
|
|
||||||
get_menu_items() {
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
label: __('Hub Settings'),
|
|
||||||
action: () => frappe.set_route('Form', 'Hub Settings'),
|
|
||||||
standard: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __('Favourites'),
|
|
||||||
action: () => frappe.set_route('Hub', 'Favourites'),
|
|
||||||
standard: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_side_bar() {
|
|
||||||
this.sidebar = new frappe.ui.Sidebar({
|
|
||||||
wrapper: this.page.wrapper.find('.layout-side-section'),
|
|
||||||
css_class: 'hub-sidebar'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_sort_selector() {
|
|
||||||
this.sort_selector = new frappe.ui.SortSelector({
|
|
||||||
parent: this.filter_area.$filter_list_wrapper,
|
|
||||||
doctype: this.doctype,
|
|
||||||
args: this.order_by,
|
|
||||||
onchange: () => this.refresh(true)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_view() {
|
|
||||||
if(frappe.route_options){
|
|
||||||
const filters = [];
|
|
||||||
for (let field in frappe.route_options) {
|
|
||||||
var value = frappe.route_options[field];
|
|
||||||
this.page.fields_dict[field].set_value(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_args() {
|
|
||||||
return {
|
|
||||||
doctype: this.doctype,
|
|
||||||
start: this.start,
|
|
||||||
limit: this.page_length,
|
|
||||||
order_by: this.order_by,
|
|
||||||
// fields: this.fields,
|
|
||||||
filters: this.get_filters_for_args()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
update_data(r) {
|
|
||||||
const data = r.message;
|
|
||||||
|
|
||||||
if (this.start === 0) {
|
|
||||||
this.data = data;
|
|
||||||
} else {
|
|
||||||
this.data = this.data.concat(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data_dict = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
freeze(toggle) { }
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.data_dict = {};
|
|
||||||
this.render_image_view();
|
|
||||||
|
|
||||||
this.setup_quick_view();
|
|
||||||
this.setup_like();
|
|
||||||
}
|
|
||||||
|
|
||||||
render_offline_card() {
|
|
||||||
let html = `<div class='page-card'>
|
|
||||||
<div class='page-card-head'>
|
|
||||||
<span class='indicator red'>
|
|
||||||
{{ _("Payment Cancelled") }}</span>
|
|
||||||
</div>
|
|
||||||
<p>${ __("Your payment is cancelled.") }</p>
|
|
||||||
<div><a href='' class='btn btn-primary btn-sm'>
|
|
||||||
${ __("Continue") }</a></div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let page = this.page.wrapper.find('.layout-side-section')
|
|
||||||
page.append(html);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_image_view() {
|
|
||||||
var html = this.data.map(this.item_html.bind(this)).join("");
|
|
||||||
|
|
||||||
if (this.start === 0) {
|
|
||||||
// ${this.getHeaderHtml()}
|
|
||||||
this.$result.html(`
|
|
||||||
<div class="image-view-container small">
|
|
||||||
${html}
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.data.length) {
|
|
||||||
this.doc = this.data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data.map(this.loadImage.bind(this));
|
|
||||||
|
|
||||||
this.data_dict = {};
|
|
||||||
this.data.map(d => {
|
|
||||||
this.data_dict[d.hub_item_code] = d;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getHeaderHtml(title, image, content) {
|
|
||||||
// let company_html =
|
|
||||||
return `
|
|
||||||
<header class="list-row-head text-muted small">
|
|
||||||
<div style="display: flex;">
|
|
||||||
<div class="list-header-icon">
|
|
||||||
<img title="${title}" alt="${title}" src="${image}">
|
|
||||||
</div>
|
|
||||||
<div class="list-header-info">
|
|
||||||
<h5>
|
|
||||||
${title}
|
|
||||||
</h5>
|
|
||||||
<span class="margin-vertical-10 level-item">
|
|
||||||
${content}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHeader() {
|
|
||||||
return `<header class="level list-row-head text-muted small">
|
|
||||||
<div class="level-left list-header-subject">
|
|
||||||
<div class="list-row-col list-subject level ">
|
|
||||||
<img title="Riadco%20Group" alt="Riadco Group" src="https://cdn.pbrd.co/images/HdaPxcg.png">
|
|
||||||
<span class="level-item">Products by Blah blah</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-left checkbox-actions">
|
|
||||||
<div class="level list-subject">
|
|
||||||
<input class="level-item list-check-all hidden-xs" type="checkbox" title="${__("Select All")}">
|
|
||||||
<span class="level-item list-header-meta"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-right">
|
|
||||||
${''}
|
|
||||||
</div>
|
|
||||||
</header>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_image_html(encoded_name, src, alt_text) {
|
|
||||||
return `<img data-name="${encoded_name}" src="${ src }" alt="${ alt_text }">`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_image_placeholder(title) {
|
|
||||||
return `<span class="placeholder-text">${ frappe.get_abbr(title) }</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadImage(item) {
|
|
||||||
item._name = encodeURI(item.name);
|
|
||||||
const encoded_name = item._name;
|
|
||||||
const title = strip_html(item[this.meta.title_field || 'name']);
|
|
||||||
|
|
||||||
let placeholder = this.get_image_placeholder(title);
|
|
||||||
let $container = this.$result.find(`.image-field[data-name="${encoded_name}"]`);
|
|
||||||
|
|
||||||
if(!item[this.imageFieldName]) {
|
|
||||||
$container.prepend(placeholder);
|
|
||||||
$container.addClass('no-image');
|
|
||||||
}
|
|
||||||
|
|
||||||
frappe.load_image(item[this.imageFieldName],
|
|
||||||
(imageObj) => {
|
|
||||||
$container.prepend(imageObj)
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
$container.prepend(placeholder);
|
|
||||||
$container.addClass('no-image');
|
|
||||||
},
|
|
||||||
(imageObj) => {
|
|
||||||
imageObj.title = encoded_name;
|
|
||||||
imageObj.alt = title;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_quick_view() {
|
|
||||||
if(this.quick_view) return;
|
|
||||||
|
|
||||||
this.quick_view = new frappe.ui.Dialog({
|
|
||||||
title: 'Quick View',
|
|
||||||
fields: this.formFields
|
|
||||||
});
|
|
||||||
this.quick_view.set_primary_action(__('Request a Quote'), () => {
|
|
||||||
this.show_rfq_modal()
|
|
||||||
.then(values => {
|
|
||||||
item.item_code = values.item_code;
|
|
||||||
delete values.item_code;
|
|
||||||
|
|
||||||
const supplier = values;
|
|
||||||
return [item, supplier];
|
|
||||||
})
|
|
||||||
.then(([item, supplier]) => {
|
|
||||||
return this.make_rfq(item, supplier, this.page.btn_primary);
|
|
||||||
})
|
|
||||||
.then(r => {
|
|
||||||
console.log(r);
|
|
||||||
if (r.message && r.message.rfq) {
|
|
||||||
this.page.btn_primary.addClass('disabled').html(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
|
|
||||||
} else {
|
|
||||||
throw r;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e); //eslint-disable-line
|
|
||||||
});
|
|
||||||
}, 'octicon octicon-plus');
|
|
||||||
|
|
||||||
this.$result.on('click', '.btn.zoom-view', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
var name = $(e.target).attr('data-name');
|
|
||||||
name = decodeURIComponent(name);
|
|
||||||
|
|
||||||
this.quick_view.set_title(name);
|
|
||||||
let values = this.data_dict[name];
|
|
||||||
this.quick_view.set_values(values);
|
|
||||||
|
|
||||||
let fields = [];
|
|
||||||
|
|
||||||
this.quick_view.show();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_like() {
|
|
||||||
if(this.setup_like_done) return;
|
|
||||||
this.setup_like_done = 1;
|
|
||||||
this.$result.on('click', '.btn.like-button', (e) => {
|
|
||||||
if($(e.target).hasClass('changing')) return;
|
|
||||||
$(e.target).addClass('changing');
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
var name = $(e.target).attr('data-name');
|
|
||||||
name = decodeURIComponent(name);
|
|
||||||
let values = this.data_dict[name];
|
|
||||||
|
|
||||||
let heart = $(e.target);
|
|
||||||
if(heart.hasClass('like-button')) {
|
|
||||||
heart = $(e.target).find('.octicon');
|
|
||||||
}
|
|
||||||
|
|
||||||
let remove = 1;
|
|
||||||
|
|
||||||
if(heart.hasClass('liked')) {
|
|
||||||
// unlike
|
|
||||||
heart.removeClass('liked');
|
|
||||||
} else {
|
|
||||||
// like
|
|
||||||
remove = 0;
|
|
||||||
heart.addClass('liked');
|
|
||||||
}
|
|
||||||
|
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.hub_node.update_wishlist_item',
|
|
||||||
args: {
|
|
||||||
item_name: values.hub_item_code,
|
|
||||||
remove: remove
|
|
||||||
},
|
|
||||||
callback: (r) => {
|
|
||||||
let message = __("Added to Favourites");
|
|
||||||
if(remove) {
|
|
||||||
message = __("Removed from Favourites");
|
|
||||||
}
|
|
||||||
frappe.show_alert(message);
|
|
||||||
},
|
|
||||||
freeze: true
|
|
||||||
});
|
|
||||||
|
|
||||||
$(e.target).removeClass('changing');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing {
|
|
||||||
constructor(opts) {
|
|
||||||
super(opts);
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.doctype = 'Hub Item';
|
|
||||||
this.page_title = __('Marketplace');
|
|
||||||
this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country'];
|
|
||||||
this.filters = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.data_dict = {};
|
|
||||||
this.render_image_view();
|
|
||||||
|
|
||||||
this.setup_quick_view();
|
|
||||||
this.setup_like();
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrap_data(response) {
|
|
||||||
let companies = response.companies.map(d => d.name);
|
|
||||||
this.custom_filter_configs = [
|
|
||||||
{
|
|
||||||
fieldtype: 'Autocomplete',
|
|
||||||
label: __('Select Company'),
|
|
||||||
condition: 'like',
|
|
||||||
fieldname: 'company_name',
|
|
||||||
options: companies
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: 'Link',
|
|
||||||
label: __('Select Country'),
|
|
||||||
options: 'Country',
|
|
||||||
condition: 'like',
|
|
||||||
fieldname: 'country'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareFormFields() {
|
|
||||||
let fieldnames = ['item_name', 'description', 'company_name', 'country'];
|
|
||||||
this.formFields = this.meta.fields
|
|
||||||
.filter(field => fieldnames.includes(field.fieldname))
|
|
||||||
.map(field => {
|
|
||||||
let {
|
|
||||||
label,
|
|
||||||
fieldname,
|
|
||||||
fieldtype,
|
|
||||||
} = field;
|
|
||||||
let read_only = 1;
|
|
||||||
return {
|
|
||||||
label,
|
|
||||||
fieldname,
|
|
||||||
fieldtype,
|
|
||||||
read_only,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.formFields.unshift({
|
|
||||||
label: 'image',
|
|
||||||
fieldname: 'image',
|
|
||||||
fieldtype: 'Attach Image'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_side_bar() {
|
|
||||||
super.setup_side_bar();
|
|
||||||
|
|
||||||
let $pitch = $(`<div class="border" style="
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 0px 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
">
|
|
||||||
<h5>Sell on HubMarket</h5>
|
|
||||||
<p>Over 2000 products listed. Register your company to start selling.</p>
|
|
||||||
</div>`);
|
|
||||||
|
|
||||||
this.sidebar.$sidebar.append($pitch);
|
|
||||||
|
|
||||||
this.category_tree = new frappe.ui.Tree({
|
|
||||||
parent: this.sidebar.$sidebar,
|
|
||||||
label: 'All Categories',
|
|
||||||
expandable: true,
|
|
||||||
|
|
||||||
args: {parent: this.current_category},
|
|
||||||
method: 'erpnext.hub_node.get_categories',
|
|
||||||
on_click: (node) => {
|
|
||||||
this.update_category(node.label);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sidebar.add_item({
|
|
||||||
label: __('Companies'),
|
|
||||||
on_click: () => frappe.set_route('Hub', 'Company')
|
|
||||||
}, undefined, true);
|
|
||||||
|
|
||||||
this.sidebar.add_item({
|
|
||||||
label: this.hub_settings.company,
|
|
||||||
on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company)
|
|
||||||
}, __("Account"));
|
|
||||||
|
|
||||||
this.sidebar.add_item({
|
|
||||||
label: __("Favourites"),
|
|
||||||
on_click: () => frappe.set_route('Hub', 'Favourites')
|
|
||||||
}, __("Account"));
|
|
||||||
|
|
||||||
this.sidebar.add_item({
|
|
||||||
label: __("Settings"),
|
|
||||||
on_click: () => frappe.set_route('Form', 'Hub Settings')
|
|
||||||
}, __("Account"));
|
|
||||||
}
|
|
||||||
|
|
||||||
update_category(label) {
|
|
||||||
this.current_category = (label=='All Categories') ? undefined : label;
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
get_filters_for_args() {
|
|
||||||
if(!this.filter_area) return;
|
|
||||||
let filters = {};
|
|
||||||
this.filter_area.get().forEach(f => {
|
|
||||||
let field = f[1] !== 'name' ? f[1] : 'item_name';
|
|
||||||
filters[field] = [f[2], f[3]];
|
|
||||||
});
|
|
||||||
if(this.current_category) {
|
|
||||||
filters['hub_category'] = this.current_category;
|
|
||||||
}
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_data(r) {
|
|
||||||
super.update_data(r);
|
|
||||||
|
|
||||||
this.data_dict = {};
|
|
||||||
this.data.map(d => {
|
|
||||||
this.data_dict[d.hub_item_code] = d;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
item_html(item) {
|
|
||||||
item._name = encodeURI(item.name);
|
|
||||||
const encoded_name = item._name;
|
|
||||||
const title = strip_html(item[this.meta.title_field || 'name']);
|
|
||||||
const _class = !item[this.imageFieldName] ? 'no-image' : '';
|
|
||||||
const route = `#Hub/Item/${item.hub_item_code}`;
|
|
||||||
const company_name = item['company_name'];
|
|
||||||
|
|
||||||
const reviewLength = (item.reviews || []).length;
|
|
||||||
const ratingAverage = reviewLength
|
|
||||||
? item.reviews
|
|
||||||
.map(r => r.rating)
|
|
||||||
.reduce((a, b) => a + b, 0)/reviewLength
|
|
||||||
: -1;
|
|
||||||
|
|
||||||
let ratingHtml = ``;
|
|
||||||
|
|
||||||
for(var i = 0; i < 5; i++) {
|
|
||||||
let starClass = 'fa-star';
|
|
||||||
if(i >= ratingAverage) starClass = 'fa-star-o';
|
|
||||||
ratingHtml += `<i class='fa fa-fw ${starClass} star-icon' data-index=${i}></i>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item_html = `
|
|
||||||
<div class="image-view-item">
|
|
||||||
<div class="image-view-header">
|
|
||||||
<div class="list-row-col list-subject ellipsis level">
|
|
||||||
<span class="level-item bold ellipsis" title="McGuffin">
|
|
||||||
<a href="${route}">${title}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted small" style="margin: 5px 0px;">
|
|
||||||
${ratingHtml}
|
|
||||||
(${reviewLength})
|
|
||||||
</div>
|
|
||||||
<div class="list-row-col">
|
|
||||||
<a href="${'#Hub/Company/'+company_name+'/Items'}"><p>${ company_name }</p></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="image-view-body">
|
|
||||||
<a data-name="${encoded_name}"
|
|
||||||
title="${encoded_name}"
|
|
||||||
href="${route}"
|
|
||||||
>
|
|
||||||
<div class="image-field ${_class}"
|
|
||||||
data-name="${encoded_name}"
|
|
||||||
>
|
|
||||||
<button class="btn btn-default zoom-view" data-name="${encoded_name}">
|
|
||||||
<i class="octicon octicon-eye" data-name="${encoded_name}"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-default like-button" data-name="${encoded_name}">
|
|
||||||
<i class="octicon octicon-heart" data-name="${encoded_name}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return item_html;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
erpnext.hub.Favourites = class Favourites extends erpnext.hub.ItemListing {
|
|
||||||
constructor(opts) {
|
|
||||||
super(opts);
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.doctype = 'Hub Item';
|
|
||||||
this.page_title = __('Favourites');
|
|
||||||
this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country'];
|
|
||||||
this.filters = [];
|
|
||||||
this.method = 'erpnext.hub_node.get_item_favourites';
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_filter_area() { }
|
|
||||||
|
|
||||||
setup_sort_selector() { }
|
|
||||||
|
|
||||||
// setupHe
|
|
||||||
|
|
||||||
getHeaderHtml() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
get_args() {
|
|
||||||
return {
|
|
||||||
start: this.start,
|
|
||||||
limit: this.page_length,
|
|
||||||
order_by: this.order_by,
|
|
||||||
fields: this.fields
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrap_data(response) { }
|
|
||||||
|
|
||||||
prepareFormFields() { }
|
|
||||||
|
|
||||||
setup_side_bar() {
|
|
||||||
this.sidebar = new frappe.ui.Sidebar({
|
|
||||||
wrapper: this.page.wrapper.find('.layout-side-section'),
|
|
||||||
css_class: 'hub-sidebar'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sidebar.add_item({
|
|
||||||
label: __('Back to Products'),
|
|
||||||
on_click: () => frappe.set_route('Hub', 'Item')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update_category(label) {
|
|
||||||
this.current_category = (label=='All Categories') ? undefined : label;
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
get_filters_for_args() {
|
|
||||||
if(!this.filter_area) return;
|
|
||||||
let filters = {};
|
|
||||||
this.filter_area.get().forEach(f => {
|
|
||||||
let field = f[1] !== 'name' ? f[1] : 'item_name';
|
|
||||||
filters[field] = [f[2], f[3]];
|
|
||||||
});
|
|
||||||
if(this.current_category) {
|
|
||||||
filters['hub_category'] = this.current_category;
|
|
||||||
}
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_data(r) {
|
|
||||||
super.update_data(r);
|
|
||||||
|
|
||||||
this.data_dict = {};
|
|
||||||
this.data.map(d => {
|
|
||||||
this.data_dict[d.hub_item_code] = d;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing {
|
|
||||||
constructor(opts) {
|
|
||||||
super(opts);
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.data_dict = {};
|
|
||||||
this.render_image_view();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_defaults() {
|
|
||||||
super.setup_defaults();
|
|
||||||
this.doctype = 'Hub Company';
|
|
||||||
this.page_title = __('Companies');
|
|
||||||
this.fields = ['company_logo', 'name', 'site_name', 'seller_city', 'seller_description', 'seller', 'country', 'company_name'];
|
|
||||||
this.filters = [];
|
|
||||||
this.custom_filter_configs = [
|
|
||||||
{
|
|
||||||
fieldtype: 'Link',
|
|
||||||
label: 'Country',
|
|
||||||
options: 'Country',
|
|
||||||
condition: 'like',
|
|
||||||
fieldname: 'country'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.imageFieldName = 'company_logo';
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_side_bar() {
|
|
||||||
this.sidebar = new frappe.ui.Sidebar({
|
|
||||||
wrapper: this.page.wrapper.find('.layout-side-section'),
|
|
||||||
css_class: 'hub-sidebar'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sidebar.add_item({
|
|
||||||
label: __('Back to Products'),
|
|
||||||
on_click: () => frappe.set_route('Hub', 'Item')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get_filters_for_args() {
|
|
||||||
let filters = {};
|
|
||||||
this.filter_area.get().forEach(f => {
|
|
||||||
let field = f[1] !== 'name' ? f[1] : 'company_name';
|
|
||||||
filters[field] = [f[2], f[3]];
|
|
||||||
});
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
item_html(company) {
|
|
||||||
company._name = encodeURI(company.company_name);
|
|
||||||
const encoded_name = company._name;
|
|
||||||
const title = strip_html(company.company_name);
|
|
||||||
const _class = !company[this.imageFieldName] ? 'no-image' : '';
|
|
||||||
const company_name = company['company_name'];
|
|
||||||
const route = `#Hub/Company/${company_name}`;
|
|
||||||
|
|
||||||
let image_html = company.company_logo ?
|
|
||||||
`<img src="${company.company_logo}"><span class="helper"></span>` :
|
|
||||||
`<div class="standard-image">${frappe.get_abbr(company.company_name)}</div>`;
|
|
||||||
|
|
||||||
let item_html = `
|
|
||||||
<div class="image-view-item">
|
|
||||||
<div class="image-view-header">
|
|
||||||
<div class="list-row-col list-subject ellipsis level">
|
|
||||||
<span class="level-item bold ellipsis" title="McGuffin">
|
|
||||||
<a href="${route}">${title}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="image-view-body">
|
|
||||||
<a data-name="${encoded_name}"
|
|
||||||
title="${encoded_name}"
|
|
||||||
href="${route}">
|
|
||||||
<div class="image-field ${_class}"
|
|
||||||
data-name="${encoded_name}">
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return item_html;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
115
erpnext/public/js/hub/marketplace.js
Normal file
115
erpnext/public/js/hub/marketplace.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import Vue from 'vue/dist/vue.js';
|
||||||
|
import './vue-plugins';
|
||||||
|
|
||||||
|
// components
|
||||||
|
import PageContainer from './PageContainer.vue';
|
||||||
|
import Sidebar from './Sidebar.vue';
|
||||||
|
import { ProfileDialog } from './components/profile_dialog';
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
import './hub_call';
|
||||||
|
import EventEmitter from './event_emitter';
|
||||||
|
|
||||||
|
frappe.provide('hub');
|
||||||
|
frappe.provide('erpnext.hub');
|
||||||
|
|
||||||
|
$.extend(erpnext.hub, EventEmitter.prototype);
|
||||||
|
|
||||||
|
erpnext.hub.Marketplace = class Marketplace {
|
||||||
|
constructor({ parent }) {
|
||||||
|
this.$parent = $(parent);
|
||||||
|
this.page = parent.page;
|
||||||
|
|
||||||
|
frappe.db.get_doc('Hub Settings')
|
||||||
|
.then(doc => {
|
||||||
|
hub.settings = doc;
|
||||||
|
const is_registered = hub.settings.registered;
|
||||||
|
const is_registered_seller = hub.settings.company_email === frappe.session.user;
|
||||||
|
this.setup_header();
|
||||||
|
this.make_sidebar();
|
||||||
|
this.make_body();
|
||||||
|
this.setup_events();
|
||||||
|
this.refresh();
|
||||||
|
if (!is_registered && !is_registered_seller && frappe.user_roles.includes('System Manager')) {
|
||||||
|
this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_header() {
|
||||||
|
this.page.set_title(__('Marketplace'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_events() {
|
||||||
|
this.$parent.on('click', '[data-route]', (e) => {
|
||||||
|
const $target = $(e.currentTarget);
|
||||||
|
const route = $target.data().route;
|
||||||
|
frappe.set_route(route);
|
||||||
|
});
|
||||||
|
|
||||||
|
// generic action handler
|
||||||
|
this.$parent.on('click', '[data-action]', e => {
|
||||||
|
const $target = $(e.currentTarget);
|
||||||
|
const action = $target.data().action;
|
||||||
|
|
||||||
|
if (action && this[action]) {
|
||||||
|
this[action].apply(this, $target);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
make_sidebar() {
|
||||||
|
this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs');
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: $('<div>').appendTo(this.$sidebar)[0],
|
||||||
|
render: h => h(Sidebar)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
make_body() {
|
||||||
|
this.$body = this.$parent.find('.layout-main-section');
|
||||||
|
this.$page_container = $('<div class="hub-page-container">').appendTo(this.$body);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '.hub-page-container',
|
||||||
|
render: h => h(PageContainer)
|
||||||
|
});
|
||||||
|
|
||||||
|
erpnext.hub.on('seller-registered', () => {
|
||||||
|
this.page.clear_primary_action()
|
||||||
|
frappe.db.get_doc('Hub Settings').then((doc)=> {
|
||||||
|
hub.settings = doc;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
show_register_dialog() {
|
||||||
|
this.register_dialog = ProfileDialog(
|
||||||
|
__('Become a Seller'),
|
||||||
|
{
|
||||||
|
label: __('Register'),
|
||||||
|
on_submit: this.register_seller.bind(this)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.register_dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
register_seller(form_values) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller',
|
||||||
|
args: form_values
|
||||||
|
}).then(() => {
|
||||||
|
this.register_dialog.hide();
|
||||||
|
frappe.set_route('marketplace', 'publish');
|
||||||
|
|
||||||
|
erpnext.hub.trigger('seller-registered');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
erpnext/public/js/hub/pages/Buying.vue
Normal file
53
erpnext/public/js/hub/pages/Buying.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section-header>
|
||||||
|
<h4>{{ __('Buying') }}</h4>
|
||||||
|
</section-header>
|
||||||
|
<div class="row" v-if="items && items.length">
|
||||||
|
<div class="col-md-7 margin-bottom"
|
||||||
|
v-for="item of items"
|
||||||
|
:key="item.name"
|
||||||
|
>
|
||||||
|
<item-list-card
|
||||||
|
:item="item"
|
||||||
|
v-route="'marketplace/buying/' + item.name"
|
||||||
|
>
|
||||||
|
<div slot="subtitle">
|
||||||
|
<span>{{item.recent_message.sender}}: </span>
|
||||||
|
<span>{{item.recent_message.content | striphtml}}</span>
|
||||||
|
</div>
|
||||||
|
</item-list-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<empty-state v-else :message="__('This page keeps track of items you want to buy from sellers.')" :centered="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import EmptyState from '../components/EmptyState.vue';
|
||||||
|
import SectionHeader from '../components/SectionHeader.vue';
|
||||||
|
import ItemListCard from '../components/ItemListCard.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SectionHeader,
|
||||||
|
ItemListCard,
|
||||||
|
EmptyState
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
items: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items_for_messages()
|
||||||
|
.then(items => {
|
||||||
|
this.items = items;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items_for_messages() {
|
||||||
|
return hub.call('get_buying_items_for_messages', {}, 'action:send_message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
59
erpnext/public/js/hub/pages/Category.vue
Normal file
59
erpnext/public/js/hub/pages/Category.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<h5>{{ page_title }}</h5>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:container_name="page_title"
|
||||||
|
:items="items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
:empty_state_message="empty_state_message"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
category: frappe.get_route()[2],
|
||||||
|
items: [],
|
||||||
|
item_id_fieldname: 'name',
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
empty_state_message: __(`No items in this category yet.`)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
page_title() {
|
||||||
|
return __(this.category);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items() {
|
||||||
|
hub.call('get_items', {
|
||||||
|
filters: {
|
||||||
|
hub_category: this.category
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((items) => {
|
||||||
|
this.items = items;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_name) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
93
erpnext/public/js/hub/pages/Home.vue
Normal file
93
erpnext/public/js/hub/pages/Home.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<search-input
|
||||||
|
:placeholder="search_placeholder"
|
||||||
|
:on_search="set_search_route"
|
||||||
|
v-model="search_value"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-for="section in sections" :key="section.title">
|
||||||
|
|
||||||
|
<section-header>
|
||||||
|
<h4>{{ section.title }}</h4>
|
||||||
|
<p v-if="section.expandable" :data-route="'marketplace/category/' + section.title">{{ 'See All' }}</p>
|
||||||
|
</section-header>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:container_name="section.title"
|
||||||
|
:items="section.items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'home-page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
item_id_fieldname: 'name',
|
||||||
|
search_value: '',
|
||||||
|
|
||||||
|
sections: [],
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
search_placeholder: __('Search for anything ...'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// refreshed
|
||||||
|
this.search_value = '';
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items() {
|
||||||
|
hub.call('get_data_for_homepage', {
|
||||||
|
country: frappe.defaults.get_user_default('country')
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.sections.push({
|
||||||
|
title: __('Explore'),
|
||||||
|
items: data.random_items
|
||||||
|
});
|
||||||
|
if (data.items_by_country.length) {
|
||||||
|
this.sections.push({
|
||||||
|
title: __('Near you'),
|
||||||
|
items: data.items_by_country
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const category_items = data.category_items;
|
||||||
|
|
||||||
|
if (category_items) {
|
||||||
|
Object.keys(category_items).map(category => {
|
||||||
|
const items = category_items[category];
|
||||||
|
|
||||||
|
this.sections.push({
|
||||||
|
title: __(category),
|
||||||
|
expandable: true,
|
||||||
|
items
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_name) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_name}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
set_search_route() {
|
||||||
|
frappe.set_route('marketplace', 'search', this.search_value);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
280
erpnext/public/js/hub/pages/Item.vue
Normal file
280
erpnext/public/js/hub/pages/Item.vue
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
v-if="init || item"
|
||||||
|
>
|
||||||
|
|
||||||
|
<detail-view
|
||||||
|
:title="title"
|
||||||
|
:image="image"
|
||||||
|
:sections="sections"
|
||||||
|
:menu_items="menu_items"
|
||||||
|
:show_skeleton="init"
|
||||||
|
>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="item_subtitle"
|
||||||
|
></detail-header-item>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="item_views_and_ratings"
|
||||||
|
></detail-header-item>
|
||||||
|
|
||||||
|
<button slot="detail-header-item"
|
||||||
|
class="btn btn-primary btn-sm margin-top"
|
||||||
|
@click="primary_action.action"
|
||||||
|
>
|
||||||
|
{{ primary_action.label }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</detail-view>
|
||||||
|
|
||||||
|
<review-area :hub_item_name="hub_item_name"></review-area>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ReviewArea from '../components/ReviewArea.vue';
|
||||||
|
import { get_rating_html } from '../components/reviews';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'item-page',
|
||||||
|
components: {
|
||||||
|
ReviewArea
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
hub_item_name: frappe.get_route()[2],
|
||||||
|
|
||||||
|
init: true,
|
||||||
|
|
||||||
|
item: null,
|
||||||
|
title: null,
|
||||||
|
image: null,
|
||||||
|
sections: [],
|
||||||
|
|
||||||
|
menu_items: [
|
||||||
|
{
|
||||||
|
label: __('Save Item'),
|
||||||
|
condition: !this.is_own_item,
|
||||||
|
action: this.add_to_saved_items
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Report this Item'),
|
||||||
|
condition: !this.is_own_item,
|
||||||
|
action: this.report_item
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Edit Details'),
|
||||||
|
condition: this.is_own_item,
|
||||||
|
action: this.edit_details
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Unpublish Item'),
|
||||||
|
condition: this.is_own_item,
|
||||||
|
action: this.unpublish_item
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
is_own_item() {
|
||||||
|
let is_own_item = false;
|
||||||
|
if(this.item) {
|
||||||
|
if(this.item.hub_seller === hub.setting.company_email) {
|
||||||
|
is_own_item = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return is_own_item;
|
||||||
|
},
|
||||||
|
|
||||||
|
item_subtitle() {
|
||||||
|
if(!this.item) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const dot_spacer = '<span aria-hidden="true"> · </span>';
|
||||||
|
let subtitle_items = [comment_when(this.item.creation)];
|
||||||
|
const rating = this.item.average_rating;
|
||||||
|
|
||||||
|
if (rating > 0) {
|
||||||
|
subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitle_items.push(this.item.company);
|
||||||
|
|
||||||
|
return subtitle_items;
|
||||||
|
},
|
||||||
|
|
||||||
|
item_views_and_ratings() {
|
||||||
|
if(!this.item) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let stats = __('No views yet');
|
||||||
|
if(this.item.view_count) {
|
||||||
|
const views_message = __(`${this.item.view_count} Views`);
|
||||||
|
|
||||||
|
const rating_html = get_rating_html(this.item.average_rating);
|
||||||
|
const rating_count = this.item.no_of_ratings > 0 ? `${this.item.no_of_ratings} reviews` : __('No reviews yet');
|
||||||
|
|
||||||
|
stats = [views_message, rating_html, rating_count];
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
},
|
||||||
|
|
||||||
|
primary_action() {
|
||||||
|
return {
|
||||||
|
label: __('Contact Seller'),
|
||||||
|
action: this.contact_seller.bind(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_item_details();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// To record a single view per session, (later)
|
||||||
|
// erpnext.hub.item_view_cache = erpnext.hub.item_view_cache || [];
|
||||||
|
// if (erpnext.hub.item_view_cache.includes(this.hub_item_name)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
this.item_received.then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
hub.call('add_item_view', {
|
||||||
|
hub_item_name: this.hub_item_name
|
||||||
|
})
|
||||||
|
// .then(() => {
|
||||||
|
// erpnext.hub.item_view_cache.push(this.hub_item_name);
|
||||||
|
// });
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_item_details() {
|
||||||
|
this.item_received = hub.call('get_item_details', { hub_item_name: this.hub_item_name })
|
||||||
|
.then(item => {
|
||||||
|
this.init = false;
|
||||||
|
this.item = item;
|
||||||
|
|
||||||
|
this.build_data();
|
||||||
|
this.make_dialogs();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
build_data() {
|
||||||
|
this.title = this.item.item_name || this.item.name;
|
||||||
|
this.image = this.item.image;
|
||||||
|
|
||||||
|
this.sections = [
|
||||||
|
{
|
||||||
|
title: __('Item Description'),
|
||||||
|
content: this.item.description
|
||||||
|
? __(this.item.description)
|
||||||
|
: __('No description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Seller Information'),
|
||||||
|
content: this.item.seller_description
|
||||||
|
? __(this.item.seller_description)
|
||||||
|
: __('No description')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
make_dialogs() {
|
||||||
|
this.make_contact_seller_dialog();
|
||||||
|
this.make_report_item_dialog();
|
||||||
|
},
|
||||||
|
|
||||||
|
add_to_saved_items() {
|
||||||
|
hub.call('add_item_to_seller_saved_items', {
|
||||||
|
hub_item_name: this.hub_item_name,
|
||||||
|
hub_seller: hub.settings.company_email
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const saved_items_link = `<b><a href="#marketplace/saved-items">${__('Saved')}</a></b>`
|
||||||
|
frappe.show_alert(saved_items_link);
|
||||||
|
erpnext.hub.trigger('action:item_save');
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
make_contact_seller_dialog() {
|
||||||
|
this.contact_seller_dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Send a message'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: 'to',
|
||||||
|
fieldtype: 'Read Only',
|
||||||
|
label: __('To'),
|
||||||
|
default: this.item.company
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Text',
|
||||||
|
fieldname: 'message',
|
||||||
|
label: __('Message')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action: ({ message }) => {
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
hub.call('send_message', {
|
||||||
|
from_seller: hub.settings.company_email,
|
||||||
|
to_seller: this.item.hub_seller,
|
||||||
|
hub_item: this.item.name,
|
||||||
|
message
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
d.hide();
|
||||||
|
frappe.set_route('marketplace', 'buying', this.item.name);
|
||||||
|
erpnext.hub.trigger('action:send_message')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
make_report_item_dialog() {
|
||||||
|
this.report_item_dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Report Item'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: __('Why do think this Item should be removed?'),
|
||||||
|
fieldtype: 'Text',
|
||||||
|
fieldname: 'message'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action: ({ message }) => {
|
||||||
|
hub.call('add_reported_item', { hub_item_name: this.item.name, message })
|
||||||
|
.then(() => {
|
||||||
|
d.hide();
|
||||||
|
frappe.show_alert(__('Item Reported'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
contact_seller() {
|
||||||
|
this.contact_seller_dialog.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
report_item() {
|
||||||
|
this.report_item_dialog.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
edit_details() {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
|
||||||
|
unpublish_item() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
105
erpnext/public/js/hub/pages/Messages.vue
Normal file
105
erpnext/public/js/hub/pages/Messages.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="item_details">
|
||||||
|
<div>
|
||||||
|
<a class="text-muted" v-route="back_link">← {{ __('Back to Messages') }}</a>
|
||||||
|
</div>
|
||||||
|
<section-header>
|
||||||
|
<div class="flex flex-column margin-bottom">
|
||||||
|
<h4>{{ item_details.item_name }}</h4>
|
||||||
|
<span class="text-muted">{{ item_details.company }}</span>
|
||||||
|
</div>
|
||||||
|
</section-header>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7">
|
||||||
|
<div class="message-container">
|
||||||
|
<div class="message-list">
|
||||||
|
<div class="level margin-bottom" v-for="message in messages" :key="message.name">
|
||||||
|
<div class="level-left ellipsis" style="width: 80%;">
|
||||||
|
<div v-html="frappe.avatar(message.sender)" />
|
||||||
|
<div style="white-space: normal;" v-html="message.content" />
|
||||||
|
</div>
|
||||||
|
<div class="level-right text-muted" v-html="frappe.datetime.comment_when(message.creation, true)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="message-input">
|
||||||
|
<comment-input @change="send_message" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import CommentInput from '../components/CommentInput.vue';
|
||||||
|
import ItemListCard from '../components/ItemListCard.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CommentInput,
|
||||||
|
ItemListCard
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message_type: frappe.get_route()[1],
|
||||||
|
item_details: null,
|
||||||
|
messages: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const hub_item_name = this.get_hub_item_name();
|
||||||
|
this.get_item_details(hub_item_name)
|
||||||
|
.then(item_details => {
|
||||||
|
this.item_details = item_details;
|
||||||
|
this.get_messages()
|
||||||
|
.then(messages => {
|
||||||
|
this.messages = messages;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
back_link() {
|
||||||
|
return 'marketplace/' + this.message_type;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
send_message(message) {
|
||||||
|
this.messages.push({
|
||||||
|
sender: hub.settings.company_email,
|
||||||
|
content: message,
|
||||||
|
creation: Date.now(),
|
||||||
|
name: frappe.utils.get_random(6)
|
||||||
|
});
|
||||||
|
hub.call('send_message', {
|
||||||
|
from_seller: hub.settings.company_email,
|
||||||
|
to_seller: this.get_against_seller(),
|
||||||
|
hub_item: this.item_details.name,
|
||||||
|
message
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get_item_details(hub_item_name) {
|
||||||
|
return hub.call('get_item_details', { hub_item_name })
|
||||||
|
},
|
||||||
|
get_messages() {
|
||||||
|
if (!this.item_details) return [];
|
||||||
|
return hub.call('get_messages', {
|
||||||
|
against_seller: this.get_against_seller(),
|
||||||
|
against_item: this.item_details.name
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get_against_seller() {
|
||||||
|
if (this.message_type === 'buying') {
|
||||||
|
return this.item_details.hub_seller;
|
||||||
|
} else if (this.message_type === 'selling') {
|
||||||
|
return frappe.get_route()[2];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get_hub_item_name() {
|
||||||
|
if (this.message_type === 'buying') {
|
||||||
|
return frappe.get_route()[2];
|
||||||
|
} else if (this.message_type === 'selling') {
|
||||||
|
return frappe.get_route()[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
36
erpnext/public/js/hub/pages/NotFound.vue
Normal file
36
erpnext/public/js/hub/pages/NotFound.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<empty-state
|
||||||
|
:message="empty_state_message"
|
||||||
|
:height="500"
|
||||||
|
:action="action"
|
||||||
|
>
|
||||||
|
</empty-state>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'not-found-page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: 'not-found',
|
||||||
|
action: {
|
||||||
|
label: __('Back to Home'),
|
||||||
|
on_click: () => {
|
||||||
|
frappe.set_route(`marketplace/home`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
empty_state_message: __(`Sorry! I could not find what you were looking for.`)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
81
erpnext/public/js/hub/pages/Profile.vue
Normal file
81
erpnext/public/js/hub/pages/Profile.vue
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
v-if="init || profile"
|
||||||
|
>
|
||||||
|
|
||||||
|
<detail-view
|
||||||
|
:title="title"
|
||||||
|
:image="image"
|
||||||
|
:sections="sections"
|
||||||
|
:show_skeleton="init"
|
||||||
|
>
|
||||||
|
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="country"
|
||||||
|
></detail-header-item>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="site_name"
|
||||||
|
></detail-header-item>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="joined_when"
|
||||||
|
></detail-header-item>
|
||||||
|
|
||||||
|
</detail-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'profile-page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
|
||||||
|
init: true,
|
||||||
|
|
||||||
|
profile: null,
|
||||||
|
title: null,
|
||||||
|
image: null,
|
||||||
|
sections: [],
|
||||||
|
|
||||||
|
country: '',
|
||||||
|
site_name: '',
|
||||||
|
joined_when: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_profile();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_profile() {
|
||||||
|
hub.call(
|
||||||
|
'get_hub_seller_profile',
|
||||||
|
{ hub_seller: hub.settings.company_email }
|
||||||
|
).then(profile => {
|
||||||
|
this.init = false;
|
||||||
|
|
||||||
|
this.profile = profile;
|
||||||
|
this.title = profile.company;
|
||||||
|
|
||||||
|
this.country = __(profile.country);
|
||||||
|
this.site_name = __(profile.site_name);
|
||||||
|
this.joined_when = __(`Joined ${comment_when(profile.creation)}`);
|
||||||
|
|
||||||
|
this.image = profile.logo;
|
||||||
|
this.sections = [
|
||||||
|
{
|
||||||
|
title: __('About the Company'),
|
||||||
|
content: profile.company_description
|
||||||
|
? __(profile.company_description)
|
||||||
|
: __('No description')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
217
erpnext/public/js/hub/pages/Publish.vue
Normal file
217
erpnext/public/js/hub/pages/Publish.vue
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<notification-message
|
||||||
|
v-if="last_sync_message"
|
||||||
|
:message="last_sync_message"
|
||||||
|
@remove-message="clear_last_sync_message"
|
||||||
|
></notification-message>
|
||||||
|
|
||||||
|
<div class="flex justify-between align-flex-end margin-bottom">
|
||||||
|
<h5>{{ page_title }}</h5>
|
||||||
|
|
||||||
|
<button class="btn btn-primary btn-sm publish-items"
|
||||||
|
:disabled="no_selected_items"
|
||||||
|
@click="publish_selected_items"
|
||||||
|
>
|
||||||
|
<span>{{ publish_button_text }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:container_name="page_title"
|
||||||
|
:items="selected_items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:is_local="true"
|
||||||
|
:editable="true"
|
||||||
|
@remove-item="remove_item_from_selection"
|
||||||
|
|
||||||
|
:empty_state_message="empty_state_message"
|
||||||
|
:empty_state_bordered="true"
|
||||||
|
:empty_state_height="80"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
|
||||||
|
<p class="text-muted">{{ valid_items_instruction }}</p>
|
||||||
|
|
||||||
|
<search-input
|
||||||
|
:placeholder="search_placeholder"
|
||||||
|
:on_search="get_valid_items"
|
||||||
|
v-model="search_value"
|
||||||
|
>
|
||||||
|
</search-input>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:items="valid_items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:is_local="true"
|
||||||
|
:on_click="show_publishing_dialog_for_item"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NotificationMessage from '../components/NotificationMessage.vue';
|
||||||
|
import { ItemPublishDialog } from '../components/item_publish_dialog';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'publish-page',
|
||||||
|
components: {
|
||||||
|
NotificationMessage
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
valid_items: [],
|
||||||
|
selected_items: [],
|
||||||
|
items_data_to_publish: {},
|
||||||
|
search_value: '',
|
||||||
|
item_id_fieldname: 'item_code',
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
// TODO: multiline translations don't work
|
||||||
|
page_title: __('Publish Items'),
|
||||||
|
search_placeholder: __('Search Items ...'),
|
||||||
|
empty_state_message: __(`No Items selected yet. Browse and click on items below to publish.`),
|
||||||
|
valid_items_instruction: __(`Only items with an image and description can be published. Please update them if an item in your inventory does not appear.`),
|
||||||
|
last_sync_message: (hub.settings.last_sync_datetime)
|
||||||
|
? __(`Last sync was
|
||||||
|
<a href="#marketplace/profile">
|
||||||
|
${comment_when(hub.settings.last_sync_datetime)}</a>.
|
||||||
|
<a href="#marketplace/published-items">
|
||||||
|
See your Published Items</a>.`)
|
||||||
|
: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
no_selected_items() {
|
||||||
|
return this.selected_items.length === 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
publish_button_text() {
|
||||||
|
const number = this.selected_items.length;
|
||||||
|
let text = __('Publish');
|
||||||
|
if(number === 1) {
|
||||||
|
text = __('Publish 1 Item');
|
||||||
|
}
|
||||||
|
if(number > 1) {
|
||||||
|
text = __('Publish {0} Items', [number]);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
|
||||||
|
items_dict() {
|
||||||
|
let items_dict = {};
|
||||||
|
this.valid_items.map(item => {
|
||||||
|
items_dict[item[this.item_id_fieldname]] = item
|
||||||
|
})
|
||||||
|
|
||||||
|
return items_dict;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_valid_items();
|
||||||
|
this.make_publishing_dialog();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_valid_items() {
|
||||||
|
frappe.call(
|
||||||
|
'erpnext.hub_node.api.get_valid_items',
|
||||||
|
{
|
||||||
|
search_value: this.search_value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((r) => {
|
||||||
|
this.valid_items = r.message;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
publish_selected_items() {
|
||||||
|
frappe.call(
|
||||||
|
'erpnext.hub_node.api.publish_selected_items',
|
||||||
|
{
|
||||||
|
items_to_publish: this.selected_items
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((r) => {
|
||||||
|
this.selected_items = [];
|
||||||
|
return frappe.db.get_doc('Hub Settings');
|
||||||
|
})
|
||||||
|
.then(doc => {
|
||||||
|
hub.settings = doc;
|
||||||
|
this.add_last_sync_message();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
add_last_sync_message() {
|
||||||
|
this.last_sync_message = __(`Last sync was
|
||||||
|
<a href="#marketplace/profile">
|
||||||
|
${comment_when(hub.settings.last_sync_datetime)}</a>.
|
||||||
|
<a href="#marketplace/published-items">
|
||||||
|
See your Published Items</a>.`);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear_last_sync_message() {
|
||||||
|
this.last_sync_message = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
remove_item_from_selection(item_code) {
|
||||||
|
this.selected_items = this.selected_items
|
||||||
|
.filter(item => item.item_code !== item_code);
|
||||||
|
},
|
||||||
|
|
||||||
|
make_publishing_dialog() {
|
||||||
|
this.item_publish_dialog = ItemPublishDialog(
|
||||||
|
{
|
||||||
|
fn: (values) => {
|
||||||
|
this.add_item_to_publish(values);
|
||||||
|
this.item_publish_dialog.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: () => {
|
||||||
|
const values = this.item_publish_dialog.get_values(true);
|
||||||
|
this.update_items_data_to_publish(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
add_item_to_publish(values) {
|
||||||
|
this.update_items_data_to_publish(values);
|
||||||
|
|
||||||
|
const item_code = values.item_code;
|
||||||
|
let item_doc = this.items_dict[item_code];
|
||||||
|
|
||||||
|
const item_to_publish = Object.assign({}, item_doc, values);
|
||||||
|
this.selected_items.push(item_to_publish);
|
||||||
|
},
|
||||||
|
|
||||||
|
update_items_data_to_publish(values) {
|
||||||
|
this.items_data_to_publish[values.item_code] = values;
|
||||||
|
},
|
||||||
|
|
||||||
|
show_publishing_dialog_for_item(item_code) {
|
||||||
|
let item_data = this.items_data_to_publish[item_code];
|
||||||
|
if(!item_data) { item_data = { item_code }; };
|
||||||
|
|
||||||
|
this.item_publish_dialog.clear();
|
||||||
|
|
||||||
|
const item_doc = this.items_dict[item_code];
|
||||||
|
if(item_doc) {
|
||||||
|
this.item_publish_dialog.fields_dict.image_list.set_data(
|
||||||
|
item_doc.attachments.map(attachment => attachment.file_url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.item_publish_dialog.set_values(item_data);
|
||||||
|
this.item_publish_dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
81
erpnext/public/js/hub/pages/PublishedItems.vue
Normal file
81
erpnext/public/js/hub/pages/PublishedItems.vue
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<section-header>
|
||||||
|
<div>
|
||||||
|
<h5>{{ page_title }}</h5>
|
||||||
|
<p v-if="items.length"
|
||||||
|
class="text-muted margin-bottom">
|
||||||
|
{{ published_items_message }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button v-if="items.length"
|
||||||
|
class="btn btn-default btn-xs publish-items"
|
||||||
|
v-route="'marketplace/publish'"
|
||||||
|
>
|
||||||
|
<span>{{ publish_button_text }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</section-header>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:container_name="page_title"
|
||||||
|
:items="items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
:empty_state_message="empty_state_message"
|
||||||
|
:empty_state_action="publish_page_action"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
items: [],
|
||||||
|
item_id_fieldname: 'name',
|
||||||
|
|
||||||
|
publish_page_action: {
|
||||||
|
label: __('Publish Your First Items'),
|
||||||
|
on_click: () => {
|
||||||
|
frappe.set_route(`marketplace/home`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
page_title: __('Published Items'),
|
||||||
|
publish_button_text: __('Publish More Items'),
|
||||||
|
published_items_message: __('You can publish upto 200 items.'),
|
||||||
|
// TODO: Add empty state action
|
||||||
|
empty_state_message: __('You haven\'t published any items yet.')
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items() {
|
||||||
|
hub.call('get_items', {
|
||||||
|
filters: {
|
||||||
|
hub_seller: hub.settings.company_email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((items) => {
|
||||||
|
this.items = items;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_name) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
111
erpnext/public/js/hub/pages/SavedItems.vue
Normal file
111
erpnext/public/js/hub/pages/SavedItems.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<h5>{{ page_title }}</h5>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:container_name="page_title"
|
||||||
|
:items="items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
:editable="true"
|
||||||
|
@remove-item="on_item_remove"
|
||||||
|
:empty_state_message="empty_state_message"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'saved-items-page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
items: [],
|
||||||
|
item_id_fieldname: 'name',
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
page_title: __('Saved Items'),
|
||||||
|
empty_state_message: __(`You haven't saved any items yet.`)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items() {
|
||||||
|
hub.call(
|
||||||
|
'get_saved_items_of_seller', {},
|
||||||
|
'action:item_save'
|
||||||
|
)
|
||||||
|
.then((items) => {
|
||||||
|
this.items = items;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_name) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_name}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
on_item_remove(hub_item_name) {
|
||||||
|
const grace_period = 5000;
|
||||||
|
let reverted = false;
|
||||||
|
let alert;
|
||||||
|
|
||||||
|
const undo_remove = () => {
|
||||||
|
this.toggle_item(hub_item_name);;
|
||||||
|
reverted = true;
|
||||||
|
alert.hide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item_name = this.items.filter(item => item.hub_item_name === hub_item_name);
|
||||||
|
|
||||||
|
alert = frappe.show_alert(__(`<span>${item_name} removed.
|
||||||
|
<a href="#" data-action="undo-remove"><b>Undo</b></a></span>`),
|
||||||
|
grace_period/1000,
|
||||||
|
{
|
||||||
|
'undo-remove': undo_remove.bind(this)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.toggle_item(hub_item_name, false);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if(!reverted) {
|
||||||
|
this.remove_item_from_saved_items(hub_item_name);
|
||||||
|
}
|
||||||
|
}, grace_period);
|
||||||
|
},
|
||||||
|
|
||||||
|
remove_item_from_saved_items(hub_item_name) {
|
||||||
|
erpnext.hub.trigger('action:item_save');
|
||||||
|
hub.call('remove_item_from_seller_saved_items', {
|
||||||
|
hub_item_name,
|
||||||
|
hub_seller: hub.settings.company_email
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.get_items();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// By default show
|
||||||
|
toggle_item(hub_item_name, show=true) {
|
||||||
|
this.items = this.items.map(item => {
|
||||||
|
if(item.name === hub_item_name) {
|
||||||
|
item.seen = show;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
70
erpnext/public/js/hub/pages/Search.vue
Normal file
70
erpnext/public/js/hub/pages/Search.vue
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<search-input
|
||||||
|
:placeholder="search_placeholder"
|
||||||
|
:on_search="set_route_and_get_items"
|
||||||
|
v-model="search_value"
|
||||||
|
>
|
||||||
|
</search-input>
|
||||||
|
|
||||||
|
<h5>{{ page_title }}</h5>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
container_name="Search"
|
||||||
|
:items="items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
:empty_state_message="empty_state_message"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
items: [],
|
||||||
|
search_value: frappe.get_route()[2],
|
||||||
|
item_id_fieldname: 'name',
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
search_placeholder: __('Search for anything ...'),
|
||||||
|
empty_state_message: __('')
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
page_title() {
|
||||||
|
return this.items.length
|
||||||
|
? __(`Results for "${this.search_value}"`)
|
||||||
|
: __('No Items found.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items() {
|
||||||
|
hub.call('get_items', { keyword: this.search_value })
|
||||||
|
.then((items) => {
|
||||||
|
this.items = items;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
set_route_and_get_items() {
|
||||||
|
frappe.set_route('marketplace', 'search', this.search_value);
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_name) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
104
erpnext/public/js/hub/pages/Seller.vue
Normal file
104
erpnext/public/js/hub/pages/Seller.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
v-if="init || profile"
|
||||||
|
>
|
||||||
|
<detail-view
|
||||||
|
:title="title"
|
||||||
|
:image="image"
|
||||||
|
:sections="sections"
|
||||||
|
:show_skeleton="init"
|
||||||
|
>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="country"
|
||||||
|
></detail-header-item>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="site_name"
|
||||||
|
></detail-header-item>
|
||||||
|
<detail-header-item slot="detail-header-item"
|
||||||
|
:value="joined_when"
|
||||||
|
></detail-header-item>
|
||||||
|
|
||||||
|
</detail-view>
|
||||||
|
|
||||||
|
<h5 v-if="profile">{{ item_container_heading }}</h5>
|
||||||
|
<item-cards-container
|
||||||
|
:container_name="item_container_heading"
|
||||||
|
:items="items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'seller-page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
seller_company: frappe.get_route()[2],
|
||||||
|
|
||||||
|
init: true,
|
||||||
|
|
||||||
|
profile: null,
|
||||||
|
items:[],
|
||||||
|
item_id_fieldname: 'name',
|
||||||
|
|
||||||
|
title: null,
|
||||||
|
image: null,
|
||||||
|
sections: [],
|
||||||
|
|
||||||
|
country: '',
|
||||||
|
site_name: '',
|
||||||
|
joined_when: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_seller_profile_and_items();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
item_container_heading() {
|
||||||
|
return __('Items by ' + this.seller_company);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_seller_profile_and_items() {
|
||||||
|
hub.call(
|
||||||
|
'get_hub_seller_page_info',
|
||||||
|
{ company: this.seller_company }
|
||||||
|
).then(data => {
|
||||||
|
this.init = false;
|
||||||
|
this.profile = data.profile;
|
||||||
|
this.items = data.items;
|
||||||
|
|
||||||
|
const profile = this.profile;
|
||||||
|
|
||||||
|
this.title = profile.company;
|
||||||
|
|
||||||
|
this.country = __(profile.country);
|
||||||
|
this.site_name = __(profile.site_name);
|
||||||
|
this.joined_when = __(`Joined ${comment_when(profile.creation)}`);
|
||||||
|
|
||||||
|
this.image = profile.logo;
|
||||||
|
this.sections = [
|
||||||
|
{
|
||||||
|
title: __('About the Company'),
|
||||||
|
content: profile.company_description
|
||||||
|
? __(profile.company_description)
|
||||||
|
: __('No description')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_name) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
66
erpnext/public/js/hub/pages/Selling.vue
Normal file
66
erpnext/public/js/hub/pages/Selling.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section-header>
|
||||||
|
<h4>{{ __('Selling') }}</h4>
|
||||||
|
</section-header>
|
||||||
|
<div class="row" v-if="items && items.length">
|
||||||
|
<div class="col-md-7"
|
||||||
|
style="margin-bottom: 30px;"
|
||||||
|
v-for="item of items"
|
||||||
|
:key="item.name"
|
||||||
|
>
|
||||||
|
<item-list-card
|
||||||
|
:item="item"
|
||||||
|
>
|
||||||
|
<div slot="subtitle">
|
||||||
|
<span class="text-muted">{{ __('{0} conversations', [item.received_messages.length]) }}</span>
|
||||||
|
</div>
|
||||||
|
</item-list-card>
|
||||||
|
<div class="hub-list-item" v-for="(message, index) in item.received_messages" :key="index"
|
||||||
|
v-route="'marketplace/selling/' + message.buyer_email + '/' + item.name"
|
||||||
|
>
|
||||||
|
<div class="hub-list-left">
|
||||||
|
<div class="hub-list-body">
|
||||||
|
<div class="hub-list-title">
|
||||||
|
{{ message.buyer }}
|
||||||
|
</div>
|
||||||
|
<div class="hub-list-subtitle">
|
||||||
|
{{ message.sender }}: {{ message.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<empty-state v-else :message="__('This page keeps track of your items in which buyers have showed some interest.')" :centered="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import EmptyState from '../components/EmptyState.vue';
|
||||||
|
import SectionHeader from '../components/SectionHeader.vue';
|
||||||
|
import ItemListCard from '../components/ItemListCard.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SectionHeader,
|
||||||
|
ItemListCard,
|
||||||
|
EmptyState
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
items: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items_for_messages()
|
||||||
|
.then(items => {
|
||||||
|
this.items = items;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items_for_messages() {
|
||||||
|
return hub.call('get_selling_items_for_messages');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
66
erpnext/public/js/hub/vue-plugins.js
Normal file
66
erpnext/public/js/hub/vue-plugins.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import Vue from 'vue/dist/vue.js';
|
||||||
|
|
||||||
|
// Global components
|
||||||
|
import ItemCardsContainer from './components/ItemCardsContainer.vue';
|
||||||
|
import SectionHeader from './components/SectionHeader.vue';
|
||||||
|
import SearchInput from './components/SearchInput.vue';
|
||||||
|
import DetailView from './components/DetailView.vue';
|
||||||
|
import DetailHeaderItem from './components/DetailHeaderItem.vue';
|
||||||
|
import EmptyState from './components/EmptyState.vue';
|
||||||
|
|
||||||
|
Vue.prototype.__ = window.__;
|
||||||
|
Vue.prototype.frappe = window.frappe;
|
||||||
|
|
||||||
|
Vue.component('item-cards-container', ItemCardsContainer);
|
||||||
|
Vue.component('section-header', SectionHeader);
|
||||||
|
Vue.component('search-input', SearchInput);
|
||||||
|
Vue.component('detail-view', DetailView);
|
||||||
|
Vue.component('detail-header-item', DetailHeaderItem);
|
||||||
|
Vue.component('empty-state', EmptyState);
|
||||||
|
|
||||||
|
Vue.directive('route', {
|
||||||
|
bind(el, binding) {
|
||||||
|
const route = binding.value;
|
||||||
|
if (!route) return;
|
||||||
|
el.classList.add('cursor-pointer');
|
||||||
|
el.dataset.route = route;
|
||||||
|
el.addEventListener('click', () => frappe.set_route(route));
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
el.classList.remove('cursor-pointer');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleImage = (el, src) => {
|
||||||
|
let img = new Image();
|
||||||
|
// add loading class
|
||||||
|
el.src = '';
|
||||||
|
el.classList.add('img-loading');
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
// image loaded, remove loading class
|
||||||
|
el.classList.remove('img-loading');
|
||||||
|
// set src
|
||||||
|
el.src = src;
|
||||||
|
}
|
||||||
|
img.onerror = () => {
|
||||||
|
el.classList.remove('img-loading');
|
||||||
|
el.classList.add('no-image');
|
||||||
|
el.src = null;
|
||||||
|
}
|
||||||
|
img.src = src;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.directive('img-src', {
|
||||||
|
bind(el, binding) {
|
||||||
|
handleImage(el, binding.value);
|
||||||
|
},
|
||||||
|
update(el, binding) {
|
||||||
|
if (binding.value === binding.oldValue) return;
|
||||||
|
handleImage(el, binding.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.filter('striphtml', function (text) {
|
||||||
|
return strip_html(text);
|
||||||
|
});
|
||||||
@@ -1,171 +1,352 @@
|
|||||||
@import "../../../../frappe/frappe/public/less/variables.less";
|
@import "../../../../frappe/frappe/public/less/variables.less";
|
||||||
|
|
||||||
body[data-route^="Hub/"] {
|
body[data-route^="marketplace/"] {
|
||||||
.hub-icon {
|
.layout-side-section {
|
||||||
width: 40px;
|
padding-top: 25px;
|
||||||
height: 40px;
|
padding-left: 5px;
|
||||||
|
padding-right: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-page-title {
|
[data-route], [data-action] {
|
||||||
margin-left: 10px;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-wrapper {
|
.layout-main-section {
|
||||||
border: 1px solid #d1d8dd;
|
border: none;
|
||||||
border-radius: 3px;
|
font-size: @text-medium;
|
||||||
padding: 12px;
|
padding-top: 25px;
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
.helper {
|
@media (max-width: @screen-xs) {
|
||||||
height: 100%;
|
padding-left: 20px;
|
||||||
display: inline-block;
|
padding-right: 20px;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree {
|
input, textarea {
|
||||||
margin: 10px 0px;
|
font-size: @text-medium;
|
||||||
padding: 0px;
|
}
|
||||||
height: 100%;
|
|
||||||
|
.progress-bar {
|
||||||
|
background-color: #89da28;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subpage-title.flex {
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card {
|
||||||
|
margin-bottom: 25px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
|
||||||
|
|
||||||
.tree.with-skeleton.opened::before {
|
|
||||||
left: 9px;
|
|
||||||
top: 14px;
|
|
||||||
height: calc(~"100% - 32px");
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-header-icon {
|
|
||||||
width: 72px;
|
|
||||||
border-radius: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 10px;
|
|
||||||
padding: 1px;
|
|
||||||
border: 1px solid @border-color;
|
border: 1px solid @border-color;
|
||||||
height: 72px;
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover .hub-card-overlay {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card.is-local {
|
||||||
|
&.active {
|
||||||
|
.hub-card-header {
|
||||||
|
background-color: #f4ffe5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon-check {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon-check {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 20px;
|
||||||
|
right: 15px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-header {
|
||||||
|
position: relative;
|
||||||
|
padding: 12px 15px;
|
||||||
|
height: 60px;
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-body {
|
||||||
|
position: relative;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-overlay {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-overlay-body {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-overlay-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-card-image {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-search-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar {
|
||||||
|
padding-top: 25px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-sidebar-item {
|
||||||
|
padding: 5px 8px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&.active, &:hover:not(.is-title) {
|
||||||
|
border-color: @border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-item-image {
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 200px;
|
||||||
|
width: 200px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
}
|
||||||
|
|
||||||
img {
|
.hub-item-skeleton-image {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
background-color: @light-bg;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 200px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-skeleton {
|
||||||
|
background-color: @light-bg;
|
||||||
|
color: @light-bg;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-item-seller img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-title {
|
||||||
|
font-size: @text-regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-form {
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-area.filled {
|
||||||
|
.empty-items-container {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.star-icon.fa-star {
|
.publish-area.empty {
|
||||||
color: @indicator-orange;
|
.hub-items-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.octicon-heart.liked {
|
.publish-area-head {
|
||||||
color: @indicator-red;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-vertical-10 {
|
.hub-list-item {
|
||||||
margin: 10px 0px;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-vertical-15 {
|
.hub-list-item:first-child {
|
||||||
margin: 15px 0px;
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.hub-list-item:last-child {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.frappe-list .result {
|
.hub-list-left {
|
||||||
min-height: 100px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.frappe-control[data-fieldtype="Attach Image"] {
|
.hub-list-right {
|
||||||
width: 140px;
|
padding-right: 15px;
|
||||||
height: 180px;
|
}
|
||||||
|
|
||||||
|
.hub-list-image {
|
||||||
|
position: relative;
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
border-right: 1px solid @border-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-list-body {
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-list-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-list-subtitle {
|
||||||
|
color: @text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selling-item-message-card {
|
||||||
|
max-width: 500px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
.selling-item-detail {
|
||||||
|
overflow: auto;
|
||||||
|
.item-image {
|
||||||
|
float: left;
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.item-name {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.received-message-container {
|
||||||
|
clear: left;
|
||||||
|
background-color: @light-bg;
|
||||||
|
.received-message {
|
||||||
|
border-top: 1px solid @border-color;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.frappe-timestamp {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
.frappe-control {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-message {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-items-container {
|
||||||
|
.hub-items-header {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-item-container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-item-review-container {
|
||||||
|
margin-top: calc(30vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-item-dropdown {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.frappe-control[data-fieldtype="Attach Image"] .form-group {
|
/* messages page */
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.frappe-control[data-fieldtype="Attach Image"] .clearfix {
|
.message-list-item {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.missing-image {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #d1d8dd;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
|
||||||
}
|
|
||||||
.missing-image .octicon {
|
|
||||||
position: relative;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(0px, -50%);
|
|
||||||
-webkit-transform: translate(0px, -50%);
|
|
||||||
}
|
|
||||||
.attach-image-display {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.img-container {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
padding: 2px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
padding: 8px 12px;
|
||||||
position: relative;
|
|
||||||
border: 1px solid #d1d8dd;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
|
||||||
}
|
|
||||||
.img-overlay {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
color: #777777;
|
|
||||||
background-color: rgba(255, 255, 255, 0.7);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.img-overlay:hover {
|
|
||||||
opacity: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-view-container {
|
&:not(.active) {
|
||||||
.image-view-body {
|
filter: grayscale(1);
|
||||||
&:hover .like-button {
|
color: @text-muted;
|
||||||
opacity: 0.7;
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: @light-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-left {
|
||||||
|
width: 30px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-body {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.like-button {
|
.message-container {
|
||||||
bottom: 10px !important;
|
display: flex;
|
||||||
left: 10px !important;
|
flex-direction: column;
|
||||||
width: 36px;
|
border: 1px solid @border-color;
|
||||||
height: 36px;
|
border-radius: 3px;
|
||||||
opacity: 0;
|
height: calc(100vh - 300px);
|
||||||
font-size: 16px;
|
justify-content: space-between;
|
||||||
color: @text-color;
|
padding: 15px;
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
// show zoom button on mobile devices
|
|
||||||
@media (max-width: @screen-xs) {
|
|
||||||
opacity: 0.5
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-view-body:hover .like-button {
|
.message-list {
|
||||||
opacity: 0.7;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-area .star-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ class Item(WebsiteGenerator):
|
|||||||
if not self.description:
|
if not self.description:
|
||||||
self.description = self.item_name
|
self.description = self.item_name
|
||||||
|
|
||||||
if self.is_sales_item and not self.get('is_item_from_hub'):
|
# if self.is_sales_item and not self.get('is_item_from_hub'):
|
||||||
self.publish_in_hub = 1
|
# self.publish_in_hub = 1
|
||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
'''set opening stock and item price'''
|
'''set opening stock and item price'''
|
||||||
|
|||||||
Reference in New Issue
Block a user