Browse Source

Fixes, old price for template, new order CP and user notify emails

Vova Tkach 5 years ago
parent
commit
fd81ed90dc
40 changed files with 824 additions and 78 deletions
  1. 6 3
      Dockerfile
  2. 7 3
      Makefile
  3. 5 0
      assets/template/404_html_file.go
  4. 5 0
      assets/template/blog_post_html_file.go
  5. 106 0
      assets/template/email_new_order_admin_html_file.go
  6. 78 0
      assets/template/email_new_order_user_html_file.go
  7. 5 0
      assets/template/index_html_file.go
  8. 5 0
      assets/template/page_html_file.go
  9. 3 2
      assets/template/shop_category_html_file.go
  10. 3 2
      assets/template/shop_html_file.go
  11. 12 3
      assets/template/shop_product_html_file.go
  12. 17 1
      assets/template/styles_css_file.go
  13. 23 21
      assets/template/template.go
  14. 22 0
      consts/consts.go
  15. 1 1
      consts/consts_version.go
  16. 8 0
      engine/basket/session.go
  17. 15 0
      engine/fetdata/fetdata.go
  18. 3 0
      engine/fetdata/shop.go
  19. 8 0
      engine/fetdata/shop_product.go
  20. 7 2
      engine/wrapper/config/config.go
  21. 63 1
      engine/wrapper/wrapper.go
  22. 5 0
      hosts/localhost/template/404.html
  23. 5 0
      hosts/localhost/template/blog-post.html
  24. 104 0
      hosts/localhost/template/email-new-order-admin.html
  25. 76 0
      hosts/localhost/template/email-new-order-user.html
  26. 5 0
      hosts/localhost/template/index.html
  27. 5 0
      hosts/localhost/template/page.html
  28. 3 2
      hosts/localhost/template/shop-category.html
  29. 12 3
      hosts/localhost/template/shop-product.html
  30. 3 2
      hosts/localhost/template/shop.html
  31. 17 1
      hosts/localhost/template/styles.css
  32. 59 9
      modules/module_index_act_mysql_setup.go
  33. 12 0
      modules/module_settings.go
  34. 8 0
      modules/module_settings_act_shop.go
  35. 1 1
      modules/module_settings_act_smtp.go
  36. 13 3
      modules/module_shop.go
  37. 64 18
      modules/module_shop_act_order.go
  38. 1 0
      support/migrate/000000001.go
  39. 21 0
      support/migrate/000000017.go
  40. 8 0
      utils/mysql_basket_structs.go

+ 6 - 3
Dockerfile

@@ -3,10 +3,13 @@ MAINTAINER Vova Tkach <vladimirok5959@gmail.com>
 
 ENV FAVE_HOST=0.0.0.0 FAVE_PORT=8080 FAVE_DIR=/app/hosts FAVE_DEBUG=false FAVE_KEEPALIVE=true
 
-ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.4.9/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz
-ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.4.9/localhost.tar.gz /app/hosts/localhost.tar.gz
+ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.5.1/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz
+ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.5.1/localhost.tar.gz /app/hosts/localhost.tar.gz
 
-RUN tar -zxf /app/fave.linux-amd64.tar.gz -C /app && \
+RUN apt-get -y update && apt-get -y upgrade && \
+ apt-get install -y ca-certificates && \
+ dpkg-reconfigure -p critical ca-certificates && \
+ tar -zxf /app/fave.linux-amd64.tar.gz -C /app && \
  tar -zxf /app/hosts/localhost.tar.gz -C /app/hosts && \
  rm /app/fave.linux-amd64.tar.gz && \
  rm /app/hosts/localhost.tar.gz && \

+ 7 - 3
Makefile

@@ -1,4 +1,4 @@
-VERSION="1.4.9"
+VERSION="1.5.1"
 
 default: debug test run
 
@@ -66,7 +66,10 @@ dockerfile:
 	@echo "ADD https://github.com/vladimirok5959/golang-fave/releases/download/v${VERSION}/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz" >> Dockerfile
 	@echo "ADD https://github.com/vladimirok5959/golang-fave/releases/download/v${VERSION}/localhost.tar.gz /app/hosts/localhost.tar.gz" >> Dockerfile
 	@echo "" >> Dockerfile
-	@echo "RUN tar -zxf /app/fave.linux-amd64.tar.gz -C /app && \\" >> Dockerfile
+	@echo "RUN apt-get -y update && apt-get -y upgrade && \\" >> Dockerfile
+	@echo " apt-get install -y ca-certificates && \\" >> Dockerfile
+	@echo " dpkg-reconfigure -p critical ca-certificates && \\" >> Dockerfile
+	@echo " tar -zxf /app/fave.linux-amd64.tar.gz -C /app && \\" >> Dockerfile
 	@echo " tar -zxf /app/hosts/localhost.tar.gz -C /app/hosts && \\" >> Dockerfile
 	@echo " rm /app/fave.linux-amd64.tar.gz && \\" >> Dockerfile
 	@echo " rm /app/hosts/localhost.tar.gz && \\" >> Dockerfile
@@ -83,7 +86,8 @@ docker-test: dockerfile
 	@-docker rm fave-test
 	@-docker rmi fave
 	docker build --rm=false --force-rm=true -t fave:latest .
-	docker run -d --name fave-test --cpus=".2" -m 200m -p 8080:8080 -t -i fave:latest /app/fave.linux-amd64
+	docker run --rm --name fave-test --cpus=".2" -m 200m -p 8080:8080 -t -i fave:latest /app/fave.linux-amd64
+	@-docker rmi fave:latest
 
 docker-img: dockerfile
 	docker build -t fave:latest .

+ 5 - 0
assets/template/404_html_file.go

@@ -3,6 +3,11 @@ package template
 var Var404HtmlFile = []byte(`{{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/template/?file=404.html" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">Error 404</h2>
 		<div class="page-content">
 			The page what you looking for "<b>{{$.Data.RequestURL}}</b>" is not found

+ 5 - 0
assets/template/blog_post_html_file.go

@@ -3,6 +3,11 @@ package template
 var VarBlogPostHtmlFile = []byte(`{{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/blog/modify/{{$.Data.Blog.Post.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">{{$.Data.Blog.Post.Name}}</h2>
 		<div class="page-content">
 			{{$.Data.Blog.Post.Briefly}}

+ 106 - 0
assets/template/email_new_order_admin_html_file.go

@@ -0,0 +1,106 @@
+package template
+
+var VarEmailNewOrderAdminHtmlFile = []byte(`<html>
+	<head>
+		<title>{{$.Else.Subject}}</title>
+	</head>
+	<body>
+		<h2>Client</h2>
+		<table border="1">
+			<tbody>
+				<tr>
+					<td><b>Last&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.LastName "" }}
+							{{$.Client.LastName}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>First&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.FirstName "" }}
+							{{$.Client.FirstName}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>Middle&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.MiddleName "" }}
+							{{$.Client.MiddleName}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>Phone</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.Phone "" }}
+							{{$.Client.Phone}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>Email</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.Email "" }}
+							{{$.Client.Email}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+			</tbody>
+		</table>
+		<div>&nbsp;</div>
+		<h2>Delivery</h2>
+		<div>
+			{{if ne $.Client.DeliveryComment "" }}
+				{{$.Client.DeliveryComment}}
+			{{else}}
+				-
+			{{end}}
+		</div>
+		<div>&nbsp;</div>
+		<h2>Order comment</h2>
+		<div>
+			{{if ne $.Client.OrderComment "" }}
+				{{$.Client.OrderComment}}
+			{{else}}
+				-
+			{{end}}
+		</div>
+		<div>&nbsp;</div>
+		<h2>Order products</h2>
+		<div>
+			<table border="1" width="100%">
+				<tbody>
+					{{range $.Basket.Products}}
+						<tr>
+							<td>
+								{{.RenderName}}
+							</td>
+							<td>
+								{{.RenderPrice}}&nbsp;{{$.Basket.Currency.Code}}&nbsp;x&nbsp;{{.RenderQuantity}}
+							</td>
+							<td>
+								{{.RenderSum}} {{$.Basket.Currency.Code}}
+							</td>
+						</tr>
+					{{end}}
+				</tbody>
+			</table>
+		</div>
+		<h2>Total: {{$.Basket.RenderTotalSum}} {{$.Basket.Currency.Code}}</h2>
+		<div>&nbsp;</div>
+		<div><a href="{{$.Else.CpOrderLink}}" target="_blank">{{$.Else.CpOrderLink}}</a></div>
+	</body>
+</html>`)

+ 78 - 0
assets/template/email_new_order_user_html_file.go

@@ -0,0 +1,78 @@
+package template
+
+var VarEmailNewOrderUserHtmlFile = []byte(`<html>
+	<head>
+		<title>{{$.Else.Subject}}</title>
+	</head>
+	<body>
+		<h2>Your contacts</h2>
+		<table border="1">
+			<tbody>
+				{{if ne $.Client.LastName "" }}
+					<tr>
+						<td><b>Last&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.LastName}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.FirstName "" }}
+					<tr>
+						<td><b>First&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.FirstName}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.MiddleName "" }}
+					<tr>
+						<td><b>Middle&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.MiddleName}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.Phone "" }}
+					<tr>
+						<td><b>Phone</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.Phone}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.Email "" }}
+					<tr>
+						<td><b>Email</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.Email}}</td>
+					</tr>
+				{{end}}
+			</tbody>
+		</table>
+		{{if ne $.Client.DeliveryComment "" }}
+			<div>&nbsp;</div>
+			<h2>Delivery</h2>
+			<div>{{$.Client.DeliveryComment}}</div>
+		{{end}}
+		{{if ne $.Client.OrderComment "" }}
+			<div>&nbsp;</div>
+			<h2>Order comment</h2>
+			<div>{{$.Client.OrderComment}}</div>
+		{{end}}
+		<div>&nbsp;</div>
+		<h2>Order products</h2>
+		<div>
+			<table border="1" width="100%">
+				<tbody>
+					{{range $.Basket.Products}}
+						<tr>
+							<td>
+								{{.RenderName}}
+							</td>
+							<td>
+								{{.RenderPrice}}&nbsp;{{$.Basket.Currency.Code}}&nbsp;x&nbsp;{{.RenderQuantity}}
+							</td>
+							<td>
+								{{.RenderSum}} {{$.Basket.Currency.Code}}
+							</td>
+						</tr>
+					{{end}}
+				</tbody>
+			</table>
+		</div>
+		<h2>Total: {{$.Basket.RenderTotalSum}} {{$.Basket.Currency.Code}}</h2>
+		<div>&nbsp;</div>
+		<div><b>Thank you for choosing our shop!</b></div>
+	</body>
+</html>`)

+ 5 - 0
assets/template/index_html_file.go

@@ -3,6 +3,11 @@ package template
 var VarIndexHtmlFile = []byte(`{{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/index/modify/{{$.Data.Page.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">{{$.Data.Page.Name}}</h2>
 		<div class="page-content">
 			{{$.Data.Page.Content}}

+ 5 - 0
assets/template/page_html_file.go

@@ -3,6 +3,11 @@ package template
 var VarPageHtmlFile = []byte(`{{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/index/modify/{{$.Data.Page.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">{{$.Data.Page.Name}}</h2>
 		<div class="page-content">
 			{{$.Data.Page.Content}}

+ 3 - 2
assets/template/shop_category_html_file.go

@@ -28,9 +28,10 @@ var VarShopCategoryHtmlFile = []byte(`{{template "header.html" .}}
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
+						{{if le .Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}
 						<a href="{{.Permalink}}" class="btn btn-success">View</a>
-						<span class="price">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
-						{{if le .Quantity 0}}<br><span class="badge badge-primary">Out of stock</span>{{end}}
+						<span class="price{{if gt .PriceOld 0.00}} price_red{{end}}">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
+						{{if gt .PriceOld 0.00}}<span class="price price_old"><strike>{{.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></span>{{end}}
 					</div>
 				</div>
 			{{end}}

+ 3 - 2
assets/template/shop_html_file.go

@@ -28,9 +28,10 @@ var VarShopHtmlFile = []byte(`{{template "header.html" .}}
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
+						{{if le .Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}
 						<a href="{{.Permalink}}" class="btn btn-success">View</a>
-						<span class="price">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
-						{{if le .Quantity 0}}<br><span class="badge badge-primary">Out of stock</span>{{end}}
+						<span class="price{{if gt .PriceOld 0.00}} price_red{{end}}">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
+						{{if gt .PriceOld 0.00}}<span class="price price_old"><strike>{{.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></span>{{end}}
 					</div>
 				</div>
 			{{end}}

+ 12 - 3
assets/template/shop_product_html_file.go

@@ -3,7 +3,12 @@ package template
 var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body product-full">
-		<h2 class="card-title">{{$.Data.Shop.Product.Name}} {{$.Data.Shop.Product.Id}}{{if le $.Data.Shop.Product.Quantity 0}} <span class="badge badge-primary">Out of stock</span>{{end}}</h2>
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/shop/modify/{{$.Data.Shop.Product.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
+		<h2 class="card-title">{{$.Data.Shop.Product.Name}} {{$.Data.Shop.Product.Id}}</h2>
 		<ul class="nav nav-tabs" id="myTab" role="tablist">
 			<li class="nav-item">
 				<a class="nav-link active" id="all-tab" data-toggle="tab" href="#all" role="tab" aria-controls="all" aria-selected="true">All about product</a>
@@ -51,7 +56,9 @@ var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 						{{end}}
 						<div class="card mt-3{{if not $.Data.Shop.Product.HaveVariations}} mt-sm-3 mt-md-0 mt-lg-0{{end}}">
 							<div class="card-body">
-								<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
+								<h3>{{if le $.Data.Shop.Product.Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}</h3>
+								{{if gt $.Data.Shop.Product.PriceOld 0.00}}<h3 class="price_old mb-0 mr-4"><strike>{{$.Data.Shop.Product.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></h3>{{end}}
+								<h3 class="price{{if gt $.Data.Shop.Product.PriceOld 0.00}} price_red{{end}} mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
 							</div>
 						</div>
 						<div class="card mt-3">
@@ -119,7 +126,9 @@ var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 							</div>
 							<div class="card mt-3">
 								<div class="card-body">
-									<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
+									<h3>{{if le $.Data.Shop.Product.Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}</h3>
+									{{if gt $.Data.Shop.Product.PriceOld 0.00}}<h3 class="price_old mb-0 mr-4"><strike>{{$.Data.Shop.Product.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></h3>{{end}}
+									<h3 class="price{{if gt $.Data.Shop.Product.PriceOld 0.00}} price_red{{end}} mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
 								</div>
 							</div>
 						</div>

+ 17 - 1
assets/template/styles_css_file.go

@@ -191,9 +191,15 @@ footer {
 }
 
 .grid-products .card-product .price {
+	display: block;
 	font-weight: bold;
 }
 
+.grid-products .card-product .price_old {
+	color: #6c757d;
+	font-size: 0.6rem;
+}
+
 .grid-products .card-product .btn {
 	float: right;
 }
@@ -204,7 +210,8 @@ footer {
 
 .grid-products .card-product .card-footer .badge {
 	position: absolute;
-	margin-top: -4px;
+	top: 0px;
+	margin-top: -23px;
 }
 
 .product-full .price {
@@ -212,6 +219,11 @@ footer {
 	vertical-align: middle;
 }
 
+.product-full .price_old {
+	color: #6c757d;
+	font-size: 1.25rem;
+}
+
 .product-full .btn-buy {
 	display: inline-block;
 	vertical-align: middle;
@@ -243,6 +255,10 @@ footer {
 	border-radius: 4px;
 }
 
+.price_red {
+	color: #fb3f4c;
+}
+
 .table-specifications .tcol-1,
 .table-specifications .tcol-2 {
 	width: 100%;

+ 23 - 21
assets/template/template.go

@@ -1,25 +1,27 @@
 package template
 
 var AllData = map[string][]byte{
-	"cached-block-3.html": VarCachedBlock_3HtmlFile,
-	"cached-block-1.html": VarCachedBlock_1HtmlFile,
-	"blog-category.html":  VarBlogCategoryHtmlFile,
-	"footer.html":         VarFooterHtmlFile,
-	"styles.css":          VarStylesCssFile,
-	"header.html":         VarHeaderHtmlFile,
-	"blog.html":           VarBlogHtmlFile,
-	"shop-product.html":   VarShopProductHtmlFile,
-	"index.html":          VarIndexHtmlFile,
-	"robots.txt":          VarRobotsTxtFile,
-	"page.html":           VarPageHtmlFile,
-	"cached-block-2.html": VarCachedBlock_2HtmlFile,
-	"404.html":            Var404HtmlFile,
-	"shop.html":           VarShopHtmlFile,
-	"shop-category.html":  VarShopCategoryHtmlFile,
-	"blog-post.html":      VarBlogPostHtmlFile,
-	"scripts.js":          VarScriptsJsFile,
-	"sidebar-left.html":   VarSidebarLeftHtmlFile,
-	"cached-block-4.html": VarCachedBlock_4HtmlFile,
-	"sidebar-right.html":  VarSidebarRightHtmlFile,
-	"cached-block-5.html": VarCachedBlock_5HtmlFile,
+	"email-new-order-user.html":  VarEmailNewOrderUserHtmlFile,
+	"cached-block-3.html":        VarCachedBlock_3HtmlFile,
+	"cached-block-1.html":        VarCachedBlock_1HtmlFile,
+	"blog-category.html":         VarBlogCategoryHtmlFile,
+	"footer.html":                VarFooterHtmlFile,
+	"styles.css":                 VarStylesCssFile,
+	"header.html":                VarHeaderHtmlFile,
+	"blog.html":                  VarBlogHtmlFile,
+	"shop-product.html":          VarShopProductHtmlFile,
+	"index.html":                 VarIndexHtmlFile,
+	"email-new-order-admin.html": VarEmailNewOrderAdminHtmlFile,
+	"robots.txt":                 VarRobotsTxtFile,
+	"page.html":                  VarPageHtmlFile,
+	"cached-block-2.html":        VarCachedBlock_2HtmlFile,
+	"404.html":                   Var404HtmlFile,
+	"shop.html":                  VarShopHtmlFile,
+	"shop-category.html":         VarShopCategoryHtmlFile,
+	"blog-post.html":             VarBlogPostHtmlFile,
+	"scripts.js":                 VarScriptsJsFile,
+	"sidebar-left.html":          VarSidebarLeftHtmlFile,
+	"cached-block-4.html":        VarCachedBlock_4HtmlFile,
+	"sidebar-right.html":         VarSidebarRightHtmlFile,
+	"cached-block-5.html":        VarCachedBlock_5HtmlFile,
 }

+ 22 - 0
consts/consts.go

@@ -99,3 +99,25 @@ type TmplDataCpBase struct {
 	UserPassword       string
 	BodyClasses        string
 }
+
+type TmplOrderClient struct {
+	LastName        string
+	FirstName       string
+	MiddleName      string
+	Phone           string
+	Email           string
+	DeliveryComment string
+	OrderComment    string
+}
+
+type TmplOrderElse struct {
+	OrderId     int64
+	Subject     string
+	CpOrderLink string
+}
+
+type TmplEmailOrder struct {
+	Basket interface{}
+	Client TmplOrderClient
+	Else   TmplOrderElse
+}

+ 1 - 1
consts/consts_version.go

@@ -1,3 +1,3 @@
 package consts
 
-const ServerVersion = "1.4.9"
+const ServerVersion = "1.5.1"

+ 8 - 0
engine/basket/session.go

@@ -377,6 +377,12 @@ func (this *session) GetAll(p *SBParam) *utils.MySql_basket {
 			A_product_id: product.Id,
 			A_price:      this.makePrice(product.price, product.currency.Id),
 			A_quantity:   product.Quantity,
+
+			RenderName:     product.Name,
+			RenderLink:     product.Link,
+			RenderPrice:    product.Price,
+			RenderQuantity: product.Quantity,
+			RenderSum:      product.Sum,
 		})
 	}
 
@@ -393,6 +399,8 @@ func (this *session) GetAll(p *SBParam) *utils.MySql_basket {
 		Currency:   &currency,
 		TotalSum:   this.totalSum,
 		TotalCount: this.TotalCount,
+
+		RenderTotalSum: this.TotalSum,
 	}
 
 	return &all

+ 15 - 0
engine/fetdata/fetdata.go

@@ -97,6 +97,21 @@ func (this *FERData) RequestGET() string {
 	return utils.ExtractGetParams(this.wrap.R.RequestURI)
 }
 
+func (this *FERData) IsUserLoggedIn() bool {
+	if this.wrap.User == nil {
+		this.wrap.LoadSessionUser()
+	}
+	return this.wrap.User != nil && this.wrap.User.A_id > 0
+}
+
+func (this *FERData) CurrentUser() *User {
+	if this.wrap.User == nil {
+		return &User{wrap: this.wrap}
+	} else {
+		return &User{wrap: this.wrap, object: this.wrap.User}
+	}
+}
+
 func (this *FERData) Module() string {
 	if this.is404 {
 		return "404"

+ 3 - 0
engine/fetdata/shop.go

@@ -53,6 +53,7 @@ func (this *Shop) load() *Shop {
 			shop_products.user,
 			shop_products.currency,
 			shop_products.price,
+			shop_products.price_old,
 			shop_products.gname,
 			shop_products.name,
 			shop_products.alias,
@@ -205,6 +206,7 @@ func (this *Shop) load() *Shop {
 				shop_products.user,
 				shop_products.currency,
 				shop_products.price,
+				shop_products.price_old,
 				shop_products.gname,
 				shop_products.name,
 				shop_products.alias,
@@ -333,6 +335,7 @@ func (this *Shop) load() *Shop {
 					&rp.A_user,
 					&rp.A_currency,
 					&rp.A_price,
+					&rp.A_price_old,
 					&rp.A_gname,
 					&rp.A_name,
 					&rp.A_alias,

+ 8 - 0
engine/fetdata/shop_product.go

@@ -269,6 +269,14 @@ func (this *ShopProduct) PriceNice() string {
 	)
 }
 
+func (this *ShopProduct) PriceOldNice() string {
+	return utils.FormatProductPrice(
+		this.PriceOld(),
+		(*this.wrap.Config).Shop.Price.Format,
+		(*this.wrap.Config).Shop.Price.Round,
+	)
+}
+
 func (this *ShopProduct) PriceFormat(format string) string {
 	return utils.Float64ToStrF(this.Price(), format)
 }

+ 7 - 2
engine/wrapper/config/config.go

@@ -41,8 +41,10 @@ type Config struct {
 				Delivery     int
 				Comment      int
 			}
-			NotifyEmail string
-			Enabled     int
+			NotifyEmail            string
+			Enabled                int
+			NewOrderEmailThemeCp   string
+			NewOrderEmailThemeUser string
 		}
 	}
 	API struct {
@@ -110,6 +112,9 @@ func (this *Config) configDefault() {
 	this.Shop.Orders.NotifyEmail = ""
 	this.Shop.Orders.Enabled = 1
 
+	this.Shop.Orders.NewOrderEmailThemeCp = "❤️ New order"
+	this.Shop.Orders.NewOrderEmailThemeUser = "❤️ Thanks for your order"
+
 	this.API.XML.Enabled = 0
 	this.API.XML.Name = ""
 	this.API.XML.Company = ""

+ 63 - 1
engine/wrapper/wrapper.go

@@ -286,7 +286,52 @@ func (this *Wrapper) ConfigSave() error {
 	return this.Config.ConfigWrite(this.DConfig + string(os.PathSeparator) + "config.json")
 }
 
-func (this *Wrapper) SendEmail(email, subject, message string) error {
+func (this *Wrapper) SendEmailUsual(email, subject, message string) error {
+	if !((*this.Config).SMTP.Host != "" && (*this.Config).SMTP.Login != "" && (*this.Config).SMTP.Password != "") {
+		return errors.New("SMTP server is not configured")
+	}
+
+	err := utils.SMTPSend(
+		(*this.Config).SMTP.Host,
+		utils.IntToStr((*this.Config).SMTP.Port),
+		(*this.Config).SMTP.Login,
+		(*this.Config).SMTP.Password,
+		subject,
+		message,
+		[]string{email},
+	)
+
+	status := 1
+	emessage := ""
+	if err != nil {
+		status = 0
+		emessage = err.Error()
+	}
+
+	if _, err := this.DB.Exec(
+		`INSERT INTO notify_mail SET
+			id = NULL,
+			email = ?,
+			subject = ?,
+			message = ?,
+			error = ?,
+			datetime = ?,
+			status = ?
+		;`,
+		email,
+		subject,
+		message,
+		emessage,
+		utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
+		status,
+	); err != nil {
+		this.LogCpError(&err)
+	}
+
+	return err
+}
+
+func (this *Wrapper) SendEmailFast(email, subject, message string) error {
 	if _, err := this.DB.Exec(
 		`INSERT INTO notify_mail SET
 			id = NULL,
@@ -307,6 +352,23 @@ func (this *Wrapper) SendEmail(email, subject, message string) error {
 	return nil
 }
 
+func (this *Wrapper) SendEmailTemplated(email, subject, tname string, data interface{}) error {
+	tmpl, err := template.New(tname + ".html").Funcs(utils.TemplateAdditionalFuncs()).ParseFiles(
+		this.DTemplate + string(os.PathSeparator) + tname + ".html",
+	)
+	if err != nil {
+		return err
+	}
+
+	var tpl bytes.Buffer
+	err = tmpl.Execute(&tpl, data)
+	if err != nil {
+		return err
+	}
+
+	return this.SendEmailFast(email, subject, string(tpl.Bytes()))
+}
+
 func (this *Wrapper) GetSessionId() string {
 	cookie, err := this.R.Cookie("session")
 	if err == nil && len(cookie.Value) == 40 {

+ 5 - 0
hosts/localhost/template/404.html

@@ -1,6 +1,11 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/template/?file=404.html" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">Error 404</h2>
 		<div class="page-content">
 			The page what you looking for "<b>{{$.Data.RequestURL}}</b>" is not found

+ 5 - 0
hosts/localhost/template/blog-post.html

@@ -1,6 +1,11 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/blog/modify/{{$.Data.Blog.Post.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">{{$.Data.Blog.Post.Name}}</h2>
 		<div class="page-content">
 			{{$.Data.Blog.Post.Briefly}}

+ 104 - 0
hosts/localhost/template/email-new-order-admin.html

@@ -0,0 +1,104 @@
+<html>
+	<head>
+		<title>{{$.Else.Subject}}</title>
+	</head>
+	<body>
+		<h2>Client</h2>
+		<table border="1">
+			<tbody>
+				<tr>
+					<td><b>Last&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.LastName "" }}
+							{{$.Client.LastName}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>First&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.FirstName "" }}
+							{{$.Client.FirstName}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>Middle&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.MiddleName "" }}
+							{{$.Client.MiddleName}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>Phone</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.Phone "" }}
+							{{$.Client.Phone}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+				<tr>
+					<td><b>Email</b>&nbsp;&nbsp;&nbsp;</td>
+					<td>
+						{{if ne $.Client.Email "" }}
+							{{$.Client.Email}}
+						{{else}}
+							-
+						{{end}}
+					</td>
+				</tr>
+			</tbody>
+		</table>
+		<div>&nbsp;</div>
+		<h2>Delivery</h2>
+		<div>
+			{{if ne $.Client.DeliveryComment "" }}
+				{{$.Client.DeliveryComment}}
+			{{else}}
+				-
+			{{end}}
+		</div>
+		<div>&nbsp;</div>
+		<h2>Order comment</h2>
+		<div>
+			{{if ne $.Client.OrderComment "" }}
+				{{$.Client.OrderComment}}
+			{{else}}
+				-
+			{{end}}
+		</div>
+		<div>&nbsp;</div>
+		<h2>Order products</h2>
+		<div>
+			<table border="1" width="100%">
+				<tbody>
+					{{range $.Basket.Products}}
+						<tr>
+							<td>
+								{{.RenderName}}
+							</td>
+							<td>
+								{{.RenderPrice}}&nbsp;{{$.Basket.Currency.Code}}&nbsp;x&nbsp;{{.RenderQuantity}}
+							</td>
+							<td>
+								{{.RenderSum}} {{$.Basket.Currency.Code}}
+							</td>
+						</tr>
+					{{end}}
+				</tbody>
+			</table>
+		</div>
+		<h2>Total: {{$.Basket.RenderTotalSum}} {{$.Basket.Currency.Code}}</h2>
+		<div>&nbsp;</div>
+		<div><a href="{{$.Else.CpOrderLink}}" target="_blank">{{$.Else.CpOrderLink}}</a></div>
+	</body>
+</html>

+ 76 - 0
hosts/localhost/template/email-new-order-user.html

@@ -0,0 +1,76 @@
+<html>
+	<head>
+		<title>{{$.Else.Subject}}</title>
+	</head>
+	<body>
+		<h2>Your contacts</h2>
+		<table border="1">
+			<tbody>
+				{{if ne $.Client.LastName "" }}
+					<tr>
+						<td><b>Last&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.LastName}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.FirstName "" }}
+					<tr>
+						<td><b>First&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.FirstName}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.MiddleName "" }}
+					<tr>
+						<td><b>Middle&nbsp;name</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.MiddleName}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.Phone "" }}
+					<tr>
+						<td><b>Phone</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.Phone}}</td>
+					</tr>
+				{{end}}
+				{{if ne $.Client.Email "" }}
+					<tr>
+						<td><b>Email</b>&nbsp;&nbsp;&nbsp;</td>
+						<td>{{$.Client.Email}}</td>
+					</tr>
+				{{end}}
+			</tbody>
+		</table>
+		{{if ne $.Client.DeliveryComment "" }}
+			<div>&nbsp;</div>
+			<h2>Delivery</h2>
+			<div>{{$.Client.DeliveryComment}}</div>
+		{{end}}
+		{{if ne $.Client.OrderComment "" }}
+			<div>&nbsp;</div>
+			<h2>Order comment</h2>
+			<div>{{$.Client.OrderComment}}</div>
+		{{end}}
+		<div>&nbsp;</div>
+		<h2>Order products</h2>
+		<div>
+			<table border="1" width="100%">
+				<tbody>
+					{{range $.Basket.Products}}
+						<tr>
+							<td>
+								{{.RenderName}}
+							</td>
+							<td>
+								{{.RenderPrice}}&nbsp;{{$.Basket.Currency.Code}}&nbsp;x&nbsp;{{.RenderQuantity}}
+							</td>
+							<td>
+								{{.RenderSum}} {{$.Basket.Currency.Code}}
+							</td>
+						</tr>
+					{{end}}
+				</tbody>
+			</table>
+		</div>
+		<h2>Total: {{$.Basket.RenderTotalSum}} {{$.Basket.Currency.Code}}</h2>
+		<div>&nbsp;</div>
+		<div><b>Thank you for choosing our shop!</b></div>
+	</body>
+</html>

+ 5 - 0
hosts/localhost/template/index.html

@@ -1,6 +1,11 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/index/modify/{{$.Data.Page.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">{{$.Data.Page.Name}}</h2>
 		<div class="page-content">
 			{{$.Data.Page.Content}}

+ 5 - 0
hosts/localhost/template/page.html

@@ -1,6 +1,11 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/index/modify/{{$.Data.Page.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
 		<h2 class="card-title">{{$.Data.Page.Name}}</h2>
 		<div class="page-content">
 			{{$.Data.Page.Content}}

+ 3 - 2
hosts/localhost/template/shop-category.html

@@ -26,9 +26,10 @@
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
+						{{if le .Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}
 						<a href="{{.Permalink}}" class="btn btn-success">View</a>
-						<span class="price">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
-						{{if le .Quantity 0}}<br><span class="badge badge-primary">Out of stock</span>{{end}}
+						<span class="price{{if gt .PriceOld 0.00}} price_red{{end}}">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
+						{{if gt .PriceOld 0.00}}<span class="price price_old"><strike>{{.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></span>{{end}}
 					</div>
 				</div>
 			{{end}}

+ 12 - 3
hosts/localhost/template/shop-product.html

@@ -1,7 +1,12 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body product-full">
-		<h2 class="card-title">{{$.Data.Shop.Product.Name}} {{$.Data.Shop.Product.Id}}{{if le $.Data.Shop.Product.Quantity 0}} <span class="badge badge-primary">Out of stock</span>{{end}}</h2>
+		{{if $.Data.IsUserLoggedIn}}
+			{{if $.Data.CurrentUser.IsAdmin}}
+				<a href="/cp/shop/modify/{{$.Data.Shop.Product.Id}}/" target="_blank" style="float:right;">Edit</a>
+			{{end}}
+		{{end}}
+		<h2 class="card-title">{{$.Data.Shop.Product.Name}} {{$.Data.Shop.Product.Id}}</h2>
 		<ul class="nav nav-tabs" id="myTab" role="tablist">
 			<li class="nav-item">
 				<a class="nav-link active" id="all-tab" data-toggle="tab" href="#all" role="tab" aria-controls="all" aria-selected="true">All about product</a>
@@ -49,7 +54,9 @@
 						{{end}}
 						<div class="card mt-3{{if not $.Data.Shop.Product.HaveVariations}} mt-sm-3 mt-md-0 mt-lg-0{{end}}">
 							<div class="card-body">
-								<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
+								<h3>{{if le $.Data.Shop.Product.Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}</h3>
+								{{if gt $.Data.Shop.Product.PriceOld 0.00}}<h3 class="price_old mb-0 mr-4"><strike>{{$.Data.Shop.Product.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></h3>{{end}}
+								<h3 class="price{{if gt $.Data.Shop.Product.PriceOld 0.00}} price_red{{end}} mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
 							</div>
 						</div>
 						<div class="card mt-3">
@@ -117,7 +124,9 @@
 							</div>
 							<div class="card mt-3">
 								<div class="card-body">
-									<h3 class="price mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
+									<h3>{{if le $.Data.Shop.Product.Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}</h3>
+									{{if gt $.Data.Shop.Product.PriceOld 0.00}}<h3 class="price_old mb-0 mr-4"><strike>{{$.Data.Shop.Product.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></h3>{{end}}
+									<h3 class="price{{if gt $.Data.Shop.Product.PriceOld 0.00}} price_red{{end}} mb-0 mr-4">{{$.Data.Shop.Product.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</h3><button class="btn btn-success btn-buy" onclick="window&&window.frontend&&frontend.ShopBasketProductAdd(this, {{$.Data.Shop.Product.Id}});return false;"{{if le $.Data.Shop.Product.Quantity 0}} disabled{{end}}>Buy</button>
 								</div>
 							</div>
 						</div>

+ 3 - 2
hosts/localhost/template/shop.html

@@ -26,9 +26,10 @@
 						<div class="card-text">{{.Briefly}}</div>
 					</div>
 					<div class="card-footer">
+						{{if le .Quantity 0}}<span class="badge badge-primary">Out of stock</span>{{end}}
 						<a href="{{.Permalink}}" class="btn btn-success">View</a>
-						<span class="price">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
-						{{if le .Quantity 0}}<br><span class="badge badge-primary">Out of stock</span>{{end}}
+						<span class="price{{if gt .PriceOld 0.00}} price_red{{end}}">{{.PriceNice}} {{$.Data.Shop.CurrentCurrency.Code}}</span>
+						{{if gt .PriceOld 0.00}}<span class="price price_old"><strike>{{.PriceOldNice}} {{$.Data.Shop.CurrentCurrency.Code}}</strike></span>{{end}}
 					</div>
 				</div>
 			{{end}}

+ 17 - 1
hosts/localhost/template/styles.css

@@ -189,9 +189,15 @@ footer {
 }
 
 .grid-products .card-product .price {
+	display: block;
 	font-weight: bold;
 }
 
+.grid-products .card-product .price_old {
+	color: #6c757d;
+	font-size: 0.6rem;
+}
+
 .grid-products .card-product .btn {
 	float: right;
 }
@@ -202,7 +208,8 @@ footer {
 
 .grid-products .card-product .card-footer .badge {
 	position: absolute;
-	margin-top: -4px;
+	top: 0px;
+	margin-top: -23px;
 }
 
 .product-full .price {
@@ -210,6 +217,11 @@ footer {
 	vertical-align: middle;
 }
 
+.product-full .price_old {
+	color: #6c757d;
+	font-size: 1.25rem;
+}
+
 .product-full .btn-buy {
 	display: inline-block;
 	vertical-align: middle;
@@ -241,6 +253,10 @@ footer {
 	border-radius: 4px;
 }
 
+.price_red {
+	color: #fb3f4c;
+}
+
 .table-specifications .tcol-1,
 .table-specifications .tcol-2 {
 	width: 100%;

+ 59 - 9
modules/module_index_act_mysql_setup.go

@@ -532,7 +532,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			return
 		}
 		if _, err = tx.Exec(
-			`INSERT INTO settings (name, value) VALUES ('database_version', '000000016');`,
+			`INSERT INTO settings (name, value) VALUES ('database_version', '000000017');`,
 		); err != nil {
 			tx.Rollback()
 			wrap.MsgError(err.Error())
@@ -574,14 +574,19 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				VALUES
 			(1, 3),
 			(1, 7),
-			(1, 9),
 			(1, 10),
 			(1, 11),
+			(1, 12),
 			(2, 3),
 			(2, 8),
-			(2, 9),
 			(2, 10),
-			(2, 11);`,
+			(2, 11),
+			(2, 12),
+			(3, 3),
+			(3, 9),
+			(3, 10),
+			(3, 11),
+			(3, 12);`,
 		); err != nil {
 			tx.Rollback()
 			wrap.MsgError(err.Error())
@@ -609,9 +614,10 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			(6, 2, '64 Gb'),
 			(7, 2, '128 Gb'),
 			(8, 2, '256 Gb'),
-			(9, 3, '4G'),
-			(10, 3, '2G'),
-			(11, 3, '3G');`,
+			(9, 2, '512 Gb'),
+			(10, 3, '4G'),
+			(11, 3, '2G'),
+			(12, 3, '3G');`,
 		); err != nil {
 			tx.Rollback()
 			wrap.MsgError(err.Error())
@@ -623,6 +629,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				user = ?,
 				currency = ?,
 				price = ?,
+				price_old = ?,
 				gname = ?,
 				name = ?,
 				alias = ?,
@@ -637,7 +644,8 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			1,
 			1,
 			1,
-			1000.00,
+			999.00,
+			1100.00,
 			"Samsung Galaxy S10",
 			"Samsung Galaxy S10 (128 Gb)",
 			"samsung-galaxy-s10-128-gb",
@@ -660,6 +668,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				user = ?,
 				currency = ?,
 				price = ?,
+				price_old = ?,
 				gname = ?,
 				name = ?,
 				alias = ?,
@@ -675,7 +684,8 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			1,
 			1,
 			1,
-			1200.00,
+			1999.00,
+			1300.00,
 			"",
 			"Samsung Galaxy S10 (256 Gb)",
 			"samsung-galaxy-s10-256-gb",
@@ -691,6 +701,46 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			wrap.MsgError(err.Error())
 			return
 		}
+		if _, err = tx.Exec(
+			`INSERT INTO shop_products SET
+				id = ?,
+				parent_id = ?,
+				user = ?,
+				currency = ?,
+				price = ?,
+				price_old = ?,
+				gname = ?,
+				name = ?,
+				alias = ?,
+				vendor = ?,
+				quantity = ?,
+				category = ?,
+				briefly = ?,
+				content = ?,
+				datetime = ?,
+				active = ?
+			;`,
+			3,
+			1,
+			1,
+			1,
+			2999.00,
+			2300.00,
+			"",
+			"Samsung Galaxy S10 (512 Gb)",
+			"samsung-galaxy-s10-512-gb",
+			"Samsung",
+			"0",
+			"3",
+			"<p>Arcu ac tortor dignissim convallis aenean et tortor. Vitae auctor eu augue ut lectus arcu. Ac turpis egestas integer eget aliquet nibh praesent.</p>",
+			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Feugiat in ante metus dictum at tempor commodo ullamcorper a. Et malesuada fames ac turpis egestas sed tempus urna et. Euismod elementum nisi quis eleifend. Nisi porta lorem mollis aliquam ut porttitor. Ac turpis egestas maecenas pharetra convallis posuere. Nunc non blandit massa enim nec dui. Commodo elit at imperdiet dui accumsan sit amet nulla. Viverra accumsan in nisl nisi scelerisque. Dui nunc mattis enim ut tellus. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Faucibus ornare suspendisse sed nisi lacus. Nulla facilisi morbi tempus iaculis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Ullamcorper sit amet risus nullam eget felis eget nunc. Volutpat sed cras ornare arcu dui vivamus. Eget magna fermentum iaculis eu non diam.</p><p>Arcu ac tortor dignissim convallis aenean et tortor. Vitae auctor eu augue ut lectus arcu. Ac turpis egestas integer eget aliquet nibh praesent. Interdum velit euismod in pellentesque massa placerat duis. Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Nisl rhoncus mattis rhoncus urna neque viverra justo. Odio ut enim blandit volutpat. Ac auctor augue mauris augue neque gravida. Ut lectus arcu bibendum at varius vel. Porttitor leo a diam sollicitudin tempor id eu nisl nunc. Dolor sit amet consectetur adipiscing elit duis tristique. Semper quis lectus nulla at volutpat diam ut. Sapien eget mi proin sed.</p>",
+			utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
+			1,
+		); err != nil {
+			tx.Rollback()
+			wrap.MsgError(err.Error())
+			return
+		}
 		if _, err = tx.Exec(
 			`INSERT INTO users (id, first_name, last_name, email, password, admin, active) VALUES (1, 'First Name', 'Last Name', 'example@example.com', '23463b99b62a72f26ed677cc556c44e8', 1, 1);`,
 		); err != nil {

+ 12 - 0
modules/module_settings.go

@@ -576,6 +576,18 @@ func (this *Modules) RegisterModule_Settings() *Module {
 					Value:   (*wrap.Config).Shop.Orders.NotifyEmail,
 					Hint:    "Example: example@gmail.com",
 				},
+				{
+					Kind:    builder.DFKText,
+					Caption: "New order email theme (CP)",
+					Name:    "new-order-email-theme-cp",
+					Value:   (*wrap.Config).Shop.Orders.NewOrderEmailThemeCp,
+				},
+				{
+					Kind:    builder.DFKText,
+					Caption: "New order email theme (User)",
+					Name:    "new-order-email-theme-user",
+					Value:   (*wrap.Config).Shop.Orders.NewOrderEmailThemeUser,
+				},
 				{
 					Kind:    builder.DFKCheckBox,
 					Caption: "Accept orders",

+ 8 - 0
modules/module_settings_act_shop.go

@@ -26,6 +26,9 @@ func (this *Modules) RegisterAction_SettingsShop() *Action {
 
 		pf_new_order_notify_email := strings.TrimSpace(wrap.R.FormValue("new-order-notify-email"))
 
+		pf_new_order_email_theme_cp := strings.TrimSpace(wrap.R.FormValue("new-order-email-theme-cp"))
+		pf_new_order_email_theme_user := strings.TrimSpace(wrap.R.FormValue("new-order-email-theme-user"))
+
 		pf_accept_orders := wrap.R.FormValue("accept-orders")
 
 		if !utils.IsNumeric(pf_price_fomat) {
@@ -97,8 +100,13 @@ func (this *Modules) RegisterAction_SettingsShop() *Action {
 			if utils.IsValidEmail(pf_new_order_notify_email) {
 				(*wrap.Config).Shop.Orders.NotifyEmail = pf_new_order_notify_email
 			}
+		} else {
+			(*wrap.Config).Shop.Orders.NotifyEmail = ""
 		}
 
+		(*wrap.Config).Shop.Orders.NewOrderEmailThemeCp = pf_new_order_email_theme_cp
+		(*wrap.Config).Shop.Orders.NewOrderEmailThemeUser = pf_new_order_email_theme_user
+
 		(*wrap.Config).Shop.Orders.Enabled = utils.StrToInt(pf_accept_orders)
 
 		if err := wrap.ConfigSave(); err != nil {

+ 1 - 1
modules/module_settings_act_smtp.go

@@ -35,7 +35,7 @@ func (this *Modules) RegisterAction_SettingsSmtp() *Action {
 
 		// Send test message
 		if pf_smtp_test_email != "" {
-			if err := wrap.SendEmail(
+			if err := wrap.SendEmailFast(
 				pf_smtp_test_email,
 				"❤️ Fave.Pro SMTP test message",
 				"Hello! This is Fave.Pro test message.<br />If you see this message, then you right configured SMTP settings!",

+ 13 - 3
modules/module_shop.go

@@ -642,10 +642,14 @@ func (this *Modules) RegisterModule_Shop() *Module {
 							name := `<a href="/cp/` + wrap.CurrModule + `/modify/` + (*values)[0] + `/">` + html.EscapeString((*values)[1]) + ` ` + html.EscapeString((*values)[0]) + `</a>`
 							alias := html.EscapeString((*values)[2])
 							parent := ``
+							outofstock := ``
 							if (*values)[7] != "" {
 								parent = `<div class="parent">&uarr;<small><a href="/cp/` + wrap.CurrModule + `/modify/` + (*values)[7] + `/">` + html.EscapeString((*values)[8]) + ` ` + (*values)[7] + `</a></small></div>`
 							}
-							return `<div>` + name + `</div><div><small>/shop/` + alias + `/</small></div>` + parent
+							if utils.StrToInt((*values)[10]) <= 0 {
+								outofstock = `<div><span class="badge badge-primary">Out of stock</span></div>`
+							}
+							return `<div>` + name + `</div><div><small>/shop/` + alias + `/</small></div>` + parent + outofstock
 						},
 					},
 					{
@@ -660,10 +664,12 @@ func (this *Modules) RegisterModule_Shop() *Module {
 						Classes:     "d-none d-md-table-cell",
 						CallBack: func(values *[]string) string {
 							price_old := ""
+							peice_styles := ""
 							if utils.StrToFloat64((*values)[9]) > 0 {
 								price_old = `<div><strike>` + utils.Float64ToStr(utils.StrToFloat64((*values)[9])) + `</strike></div>`
+								peice_styles = ` style="color:#fb3f4c;"`
 							}
-							return price_old + `<div>` + utils.Float64ToStr(utils.StrToFloat64((*values)[4])) + `</div>` +
+							return price_old + `<div` + peice_styles + `>` + utils.Float64ToStr(utils.StrToFloat64((*values)[4])) + `</div>` +
 								`<div><small>` + currencies[utils.StrToInt((*values)[3])] + `</small></div>`
 						},
 					},
@@ -696,6 +702,9 @@ func (this *Modules) RegisterModule_Shop() *Module {
 					{
 						DBField: "price_old",
 					},
+					{
+						DBField: "quantity",
+					},
 				},
 				func(values *[]string) string {
 					return builder.DataTableAction(&[]builder.DataTableActionRow{
@@ -738,7 +747,8 @@ func (this *Modules) RegisterModule_Shop() *Module {
 							shop_products.active,
 							shop_products.parent_id,
 							spp.name AS pname,
-							shop_products.price_old
+							shop_products.price_old,
+							shop_products.quantity
 						FROM
 							shop_products
 							LEFT JOIN shop_products AS spp ON spp.id = shop_products.parent_id

+ 64 - 18
modules/module_shop_act_order.go

@@ -3,6 +3,7 @@ package modules
 import (
 	"strings"
 
+	"golang-fave/consts"
 	"golang-fave/engine/basket"
 	"golang-fave/engine/wrapper"
 	"golang-fave/utils"
@@ -30,34 +31,34 @@ func (this *Modules) RegisterAction_ShopOrder() *Action {
 			return
 		}
 
-		pf_client_last_name := wrap.R.FormValue("client_last_name")
-		pf_client_first_name := wrap.R.FormValue("client_first_name")
-		pf_client_middle_name := wrap.R.FormValue("client_middle_name")
-		pf_client_phone := wrap.R.FormValue("client_phone")
-		pf_client_email := wrap.R.FormValue("client_email")
-		pf_client_delivery_comment := wrap.R.FormValue("client_delivery_comment")
-		pf_client_order_comment := wrap.R.FormValue("client_order_comment")
+		pf_client_last_name := strings.TrimSpace(wrap.R.FormValue("client_last_name"))
+		pf_client_first_name := strings.TrimSpace(wrap.R.FormValue("client_first_name"))
+		pf_client_middle_name := strings.TrimSpace(wrap.R.FormValue("client_middle_name"))
+		pf_client_phone := strings.TrimSpace(wrap.R.FormValue("client_phone"))
+		pf_client_email := strings.TrimSpace(wrap.R.FormValue("client_email"))
+		pf_client_delivery_comment := strings.TrimSpace(wrap.R.FormValue("client_delivery_comment"))
+		pf_client_order_comment := strings.TrimSpace(wrap.R.FormValue("client_order_comment"))
 
 		if (*wrap.Config).Shop.Orders.RequiredFields.LastName != 0 {
-			if strings.TrimSpace(pf_client_last_name) == "" {
+			if pf_client_last_name == "" {
 				wrap.Write(`{"error": true, "field": "client_last_name", "variable": "ShopOrderEmptyLastName"}`)
 				return
 			}
 		}
 		if (*wrap.Config).Shop.Orders.RequiredFields.FirstName != 0 {
-			if strings.TrimSpace(pf_client_first_name) == "" {
+			if pf_client_first_name == "" {
 				wrap.Write(`{"error": true, "field": "client_first_name", "variable": "ShopOrderEmptyFirstName"}`)
 				return
 			}
 		}
 		if (*wrap.Config).Shop.Orders.RequiredFields.MiddleName != 0 {
-			if strings.TrimSpace(pf_client_middle_name) == "" {
+			if pf_client_middle_name == "" {
 				wrap.Write(`{"error": true, "field": "client_middle_name", "variable": "ShopOrderEmptyMiddleName"}`)
 				return
 			}
 		}
 		if (*wrap.Config).Shop.Orders.RequiredFields.MobilePhone != 0 {
-			if strings.TrimSpace(pf_client_phone) == "" {
+			if pf_client_phone == "" {
 				wrap.Write(`{"error": true, "field": "client_phone", "variable": "ShopOrderEmptyMobilePhone"}`)
 				return
 			}
@@ -67,7 +68,7 @@ func (this *Modules) RegisterAction_ShopOrder() *Action {
 			}
 		}
 		if (*wrap.Config).Shop.Orders.RequiredFields.EmailAddress != 0 {
-			if strings.TrimSpace(pf_client_email) == "" {
+			if pf_client_email == "" {
 				wrap.Write(`{"error": true, "field": "client_email", "variable": "ShopOrderEmptyEmailAddress"}`)
 				return
 			}
@@ -77,13 +78,13 @@ func (this *Modules) RegisterAction_ShopOrder() *Action {
 			}
 		}
 		if (*wrap.Config).Shop.Orders.RequiredFields.Delivery != 0 {
-			if strings.TrimSpace(pf_client_delivery_comment) == "" {
+			if pf_client_delivery_comment == "" {
 				wrap.Write(`{"error": true, "field": "client_delivery_comment", "variable": "ShopOrderEmptyDelivery"}`)
 				return
 			}
 		}
 		if (*wrap.Config).Shop.Orders.RequiredFields.Comment != 0 {
-			if strings.TrimSpace(pf_client_order_comment) == "" {
+			if pf_client_order_comment == "" {
 				wrap.Write(`{"error": true, "field": "client_order_comment", "variable": "ShopOrderEmptyComment"}`)
 				return
 			}
@@ -154,12 +155,57 @@ func (this *Modules) RegisterAction_ShopOrder() *Action {
 				}
 			}
 
-			// Send notify email
+			// Send notify email to owner
 			if (*wrap.Config).Shop.Orders.NotifyEmail != "" {
-				if err := wrap.SendEmail(
+				if err := wrap.SendEmailTemplated(
 					(*wrap.Config).Shop.Orders.NotifyEmail,
-					"❤️ New Order ("+wrap.Host+":"+wrap.Port+")",
-					"You have new order in shop on host: <a href=\"http://"+wrap.Host+":"+wrap.Port+"/\">http://"+wrap.Host+":"+wrap.Port+"/</a>",
+					(*wrap.Config).Shop.Orders.NewOrderEmailThemeCp+" #"+utils.Int64ToStr(lastID)+" ("+wrap.Host+":"+wrap.Port+")",
+					"email-new-order-admin",
+					consts.TmplEmailOrder{
+						Basket: bdata,
+						Client: consts.TmplOrderClient{
+							LastName:        pf_client_last_name,
+							FirstName:       pf_client_first_name,
+							MiddleName:      pf_client_middle_name,
+							Phone:           pf_client_phone,
+							Email:           pf_client_email,
+							DeliveryComment: pf_client_delivery_comment,
+							OrderComment:    pf_client_order_comment,
+						},
+						Else: consts.TmplOrderElse{
+							OrderId:     lastID,
+							Subject:     (*wrap.Config).Shop.Orders.NewOrderEmailThemeCp + " #" + utils.Int64ToStr(lastID) + " (" + wrap.Host + ":" + wrap.Port + ")",
+							CpOrderLink: "http://" + wrap.Host + ":" + wrap.Port + "/cp/shop/orders-modify/" + utils.Int64ToStr(lastID) + "/",
+						},
+					},
+				); err != nil {
+					return err
+				}
+			}
+
+			// Send notify email to client
+			if pf_client_email != "" {
+				if err := wrap.SendEmailTemplated(
+					pf_client_email,
+					(*wrap.Config).Shop.Orders.NewOrderEmailThemeUser+" #"+utils.Int64ToStr(lastID),
+					"email-new-order-user",
+					consts.TmplEmailOrder{
+						Basket: bdata,
+						Client: consts.TmplOrderClient{
+							LastName:        pf_client_last_name,
+							FirstName:       pf_client_first_name,
+							MiddleName:      pf_client_middle_name,
+							Phone:           pf_client_phone,
+							Email:           pf_client_email,
+							DeliveryComment: pf_client_delivery_comment,
+							OrderComment:    pf_client_order_comment,
+						},
+						Else: consts.TmplOrderElse{
+							OrderId:     lastID,
+							Subject:     (*wrap.Config).Shop.Orders.NewOrderEmailThemeUser + " #" + utils.Int64ToStr(lastID),
+							CpOrderLink: "http://" + wrap.Host + ":" + wrap.Port + "/cp/shop/orders-modify/" + utils.Int64ToStr(lastID) + "/",
+						},
+					},
 				); err != nil {
 					return err
 				}

+ 1 - 0
support/migrate/000000001.go

@@ -22,4 +22,5 @@ var Migrations = map[string]func(*sqlw.DB, string) error{
 	"000000014": Migrate_000000014,
 	"000000015": Migrate_000000015,
 	"000000016": Migrate_000000016,
+	"000000017": Migrate_000000017,
 }

+ 21 - 0
support/migrate/000000017.go

@@ -0,0 +1,21 @@
+package migrate
+
+import (
+	"io/ioutil"
+	"os"
+
+	ThemeFiles "golang-fave/assets/template"
+	"golang-fave/engine/sqlw"
+)
+
+func Migrate_000000017(db *sqlw.DB, host string) error {
+	if err := ioutil.WriteFile(host+string(os.PathSeparator)+"/template/email-new-order-admin.html", ThemeFiles.AllData["email-new-order-admin.html"], 0664); err != nil {
+		return err
+	}
+
+	if err := ioutil.WriteFile(host+string(os.PathSeparator)+"/template/email-new-order-user.html", ThemeFiles.AllData["email-new-order-user.html"], 0664); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 8 - 0
utils/mysql_basket_structs.go

@@ -12,6 +12,12 @@ type MySql_basket_product struct {
 	A_product_id int
 	A_price      float64
 	A_quantity   int
+
+	RenderName     string
+	RenderLink     string
+	RenderPrice    string
+	RenderQuantity int
+	RenderSum      string
 }
 
 type MySql_basket struct {
@@ -19,4 +25,6 @@ type MySql_basket struct {
 	Currency   *MySql_basket_currency
 	TotalSum   float64
 	TotalCount int
+
+	RenderTotalSum string
 }