Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers
This commit is contained in:
@@ -21,7 +21,7 @@ func New() *AddressesService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AddressesService) GetTemplate(country_id uint) (model.AddressField, error) {
|
||||
func (s *AddressesService) GetTemplate(country_id uint) (model.AddressUnparsed, error) {
|
||||
switch country_id {
|
||||
|
||||
case 1: // Poland
|
||||
@@ -49,7 +49,7 @@ func (s *AddressesService) AddNewAddress(user_id uint, address_info string, coun
|
||||
return responseErrors.ErrMaxAmtOfAddressesReached
|
||||
}
|
||||
|
||||
_, err = s.validateAddressJson(address_info, country_id)
|
||||
_, err = s.ValidateAddressJson(address_info, country_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (s *AddressesService) ModifyAddress(user_id uint, address_id uint, address_
|
||||
return responseErrors.ErrUserHasNoSuchAddress
|
||||
}
|
||||
|
||||
_, err = s.validateAddressJson(address_info, country_id)
|
||||
_, err = s.ValidateAddressJson(address_info, country_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,30 +74,23 @@ func (s *AddressesService) ModifyAddress(user_id uint, address_id uint, address_
|
||||
return s.repo.UpdateAddress(user_id, address_id, address_info, country_id)
|
||||
}
|
||||
|
||||
func (s *AddressesService) RetrieveAddressesInfo(user_id uint) (*[]model.AddressUnparsed, error) {
|
||||
parsed_addresses, err := s.repo.RetrieveAddresses(user_id)
|
||||
func (s *AddressesService) RetrieveAddresses(user_id uint) (*[]model.Address, error) {
|
||||
addresses, err := s.repo.RetrieveAddresses(user_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var unparsed_addresses []model.AddressUnparsed
|
||||
|
||||
for i := 0; i < len(*parsed_addresses); i++ {
|
||||
var next_address model.AddressUnparsed
|
||||
next_address.ID = (*parsed_addresses)[i].ID
|
||||
next_address.CustomerID = (*parsed_addresses)[i].CustomerID
|
||||
next_address.CountryID = (*parsed_addresses)[i].CountryID
|
||||
|
||||
next_address.AddressInfo, err = s.validateAddressJson((*parsed_addresses)[i].AddressInfo, next_address.CountryID)
|
||||
for i := 0; i < len(*addresses); i++ {
|
||||
address_unparsed, err := s.ValidateAddressJson((*addresses)[i].AddressString, (*addresses)[i].CountryID)
|
||||
// log such errors
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
}
|
||||
|
||||
unparsed_addresses = append(unparsed_addresses, next_address)
|
||||
(*addresses)[i].AddressUnparsed = &address_unparsed
|
||||
}
|
||||
|
||||
return &unparsed_addresses, nil
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func (s *AddressesService) DeleteAddress(user_id uint, address_id uint) error {
|
||||
@@ -112,7 +105,7 @@ func (s *AddressesService) DeleteAddress(user_id uint, address_id uint) error {
|
||||
}
|
||||
|
||||
// validateAddressJson makes sure that the info string represents a valid json of address in given country
|
||||
func (s *AddressesService) validateAddressJson(info string, country_id uint) (model.AddressField, error) {
|
||||
func (s *AddressesService) ValidateAddressJson(info string, country_id uint) (model.AddressUnparsed, error) {
|
||||
dec := json.NewDecoder(strings.NewReader(info))
|
||||
dec.DisallowUnknownFields()
|
||||
|
||||
|
||||
@@ -34,12 +34,24 @@ func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
||||
return cart, nil
|
||||
}
|
||||
|
||||
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
func (s *CartsService) RemoveCart(user_id uint, cart_id uint) error {
|
||||
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if amt != 1 {
|
||||
if !exists {
|
||||
return responseErrors.ErrUserHasNoSuchCart
|
||||
}
|
||||
|
||||
return s.repo.RemoveCart(user_id, cart_id)
|
||||
}
|
||||
|
||||
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return responseErrors.ErrUserHasNoSuchCart
|
||||
}
|
||||
|
||||
@@ -51,11 +63,11 @@ func (s *CartsService) RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, er
|
||||
}
|
||||
|
||||
func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if amt != 1 {
|
||||
if !exists {
|
||||
return nil, responseErrors.ErrUserHasNoSuchCart
|
||||
}
|
||||
|
||||
@@ -63,19 +75,19 @@ func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
|
||||
}
|
||||
|
||||
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if amt != 1 {
|
||||
if !exists {
|
||||
return responseErrors.ErrUserHasNoSuchCart
|
||||
}
|
||||
|
||||
amt, err = s.repo.CheckProductExists(product_id, product_attribute_id)
|
||||
exists, err = s.repo.CheckProductExists(product_id, product_attribute_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if amt != 1 {
|
||||
if !exists {
|
||||
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,18 @@ func (s *EmailService) SendNewUserAdminNotification(userEmail, userName, baseURL
|
||||
return s.SendEmail(s.config.AdminEmail, subject, body)
|
||||
}
|
||||
|
||||
// SendNewOrderPlacedNotification sends an email to admin when new order is placed
|
||||
func (s *EmailService) SendNewOrderPlacedNotification(userID uint) error {
|
||||
if s.config.AdminEmail == "" {
|
||||
return nil // No admin email configured
|
||||
}
|
||||
|
||||
subject := "New Order Created"
|
||||
body := s.newOrderPlacedTemplate(userID)
|
||||
|
||||
return s.SendEmail(s.config.AdminEmail, subject, body)
|
||||
}
|
||||
|
||||
// verificationEmailTemplate returns the HTML template for email verification
|
||||
func (s *EmailService) verificationEmailTemplate(name, verificationURL string, langID uint) string {
|
||||
buf := bytes.Buffer{}
|
||||
@@ -137,3 +149,10 @@ func (s *EmailService) newUserAdminNotificationTemplate(userEmail, userName, bas
|
||||
emails.EmailAdminNotificationWrapper(view.EmailLayout[view.EmailAdminNotificationData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailAdminNotificationData{UserEmail: userEmail, UserName: userName, BaseURL: baseURL}}).Render(context.Background(), &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
||||
func (s *EmailService) newOrderPlacedTemplate(userID uint) string {
|
||||
buf := bytes.Buffer{}
|
||||
emails.EmailNewOrderPlacedWrapper(view.EmailLayout[view.EmailNewOrderPlacedData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailNewOrderPlacedData{UserID: userID}}).Render(context.Background(), &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
|
||||
searchrepo "git.ma-al.com/goc_daniel/b2b/app/repos/searchRepo"
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
)
|
||||
@@ -20,8 +20,8 @@ type MeiliIndexSettings struct {
|
||||
}
|
||||
|
||||
type MeiliService struct {
|
||||
productDescriptionRepo productDescriptionRepo.UIProductDescriptionRepo
|
||||
meiliClient meilisearch.ServiceManager
|
||||
searchRepo searchrepo.UISearchRepo
|
||||
meiliClient meilisearch.ServiceManager
|
||||
}
|
||||
|
||||
func New() *MeiliService {
|
||||
@@ -32,8 +32,8 @@ func New() *MeiliService {
|
||||
)
|
||||
|
||||
return &MeiliService{
|
||||
meiliClient: client,
|
||||
productDescriptionRepo: productDescriptionRepo.New(),
|
||||
meiliClient: client,
|
||||
searchRepo: searchrepo.New(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
||||
|
||||
for {
|
||||
// Get batch of products from repo (includes scanning)
|
||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
||||
products, err := s.searchRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
|
||||
}
|
||||
|
||||
@@ -3,31 +3,45 @@ package menuService
|
||||
import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/categoriesRepo"
|
||||
categoryrepo "git.ma-al.com/goc_daniel/b2b/app/repos/categoryRepo"
|
||||
routesRepo "git.ma-al.com/goc_daniel/b2b/app/repos/routesRepo"
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||
)
|
||||
|
||||
type MenuService struct {
|
||||
categoriesRepo categoriesRepo.UICategoriesRepo
|
||||
routesRepo routesRepo.UIRoutesRepo
|
||||
categoryRepo categoryrepo.UICategoryRepo
|
||||
routesRepo routesRepo.UIRoutesRepo
|
||||
}
|
||||
|
||||
func New() *MenuService {
|
||||
return &MenuService{
|
||||
categoriesRepo: categoriesRepo.New(),
|
||||
routesRepo: routesRepo.New(),
|
||||
categoryRepo: categoryrepo.New(),
|
||||
routesRepo: routesRepo.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) {
|
||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||
all_categories, err := s.categoryRepo.RetrieveMenuCategories(id_lang)
|
||||
if err != nil {
|
||||
return &model.Category{}, err
|
||||
}
|
||||
|
||||
// remove blacklisted categories
|
||||
// to do so, we detach them from the main tree
|
||||
for i := 0; i < len(all_categories); i++ {
|
||||
if slices.Contains(constdata.CATEGORY_BLACKLIST, all_categories[i].CategoryID) {
|
||||
all_categories[i].ParentID = all_categories[i].CategoryID
|
||||
}
|
||||
}
|
||||
|
||||
iso_code := all_categories[0].IsoCode
|
||||
s.appendAdditional(&all_categories, id_lang, iso_code)
|
||||
|
||||
// find the root
|
||||
root_index := 0
|
||||
root_found := false
|
||||
@@ -98,7 +112,7 @@ func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) mod
|
||||
normal.CategoryID = scanned.CategoryID
|
||||
normal.Label = scanned.Name
|
||||
// normal.Active = scanned.Active == 1
|
||||
normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
||||
normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode, Filter: scanned.Filter}
|
||||
normal.Children = []model.Category{}
|
||||
return normal
|
||||
}
|
||||
@@ -114,11 +128,14 @@ func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
||||
|
||||
func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uint, id_lang uint) ([]model.CategoryInBreadcrumb, error) {
|
||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||
all_categories, err := s.categoryRepo.RetrieveMenuCategories(id_lang)
|
||||
if err != nil {
|
||||
return []model.CategoryInBreadcrumb{}, err
|
||||
}
|
||||
|
||||
iso_code := all_categories[0].IsoCode
|
||||
s.appendAdditional(&all_categories, id_lang, iso_code)
|
||||
|
||||
breadcrumb := []model.CategoryInBreadcrumb{}
|
||||
|
||||
start_index := 0
|
||||
@@ -211,3 +228,24 @@ func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopM
|
||||
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) {
|
||||
for i := 0; i < len(*all_categories); i++ {
|
||||
(*all_categories)[i].Filter = "category_id_in=" + strconv.Itoa(int((*all_categories)[i].CategoryID))
|
||||
}
|
||||
|
||||
var additional model.ScannedCategory
|
||||
additional.CategoryID = 10001
|
||||
additional.Name = "New Products"
|
||||
additional.Active = 1
|
||||
additional.Position = 10
|
||||
additional.ParentID = 2
|
||||
additional.IsRoot = 0
|
||||
additional.LinkRewrite = i18n.T___(id_lang, "category.new_products")
|
||||
additional.IsoCode = iso_code
|
||||
|
||||
additional.Visited = false
|
||||
additional.Filter = "is_new_in=true"
|
||||
|
||||
*all_categories = append(*all_categories, additional)
|
||||
}
|
||||
|
||||
145
app/service/orderService/orderService.go
Normal file
145
app/service/orderService/orderService.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package orderService
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/cartsRepo"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/ordersRepo"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/service/addressesService"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||
)
|
||||
|
||||
type OrderService struct {
|
||||
ordersRepo ordersRepo.UIOrdersRepo
|
||||
cartsRepo cartsRepo.UICartsRepo
|
||||
addressesService *addressesService.AddressesService
|
||||
emailService *emailService.EmailService
|
||||
}
|
||||
|
||||
func New() *OrderService {
|
||||
return &OrderService{
|
||||
ordersRepo: ordersRepo.New(),
|
||||
cartsRepo: cartsRepo.New(),
|
||||
addressesService: addressesService.New(),
|
||||
emailService: emailService.NewEmailService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OrderService) Find(user *model.Customer, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error) {
|
||||
if !user.HasPermission(perms.OrdersViewAll) {
|
||||
// append filter to view only this user's orders
|
||||
idStr := strconv.FormatUint(uint64(user.ID), 10)
|
||||
filt.Append(filters.Where("b2b_customer_orders.user_id = " + idStr))
|
||||
}
|
||||
|
||||
list, err := s.ordersRepo.Find(user.ID, p, filt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(list.Items); i++ {
|
||||
address_unparsed, err := s.addressesService.ValidateAddressJson(list.Items[i].AddressString, list.Items[i].CountryID)
|
||||
// log such errors
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
}
|
||||
|
||||
list.Items[i].AddressUnparsed = &address_unparsed
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, name string, country_id uint, address_info string) error {
|
||||
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := s.cartsRepo.UserHasCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return responseErrors.ErrUserHasNoSuchCart
|
||||
}
|
||||
|
||||
cart, err := s.cartsRepo.RetrieveCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cart.Products) == 0 {
|
||||
return responseErrors.ErrEmptyCart
|
||||
}
|
||||
|
||||
if name == "" && cart.Name != nil {
|
||||
name = *cart.Name
|
||||
}
|
||||
|
||||
// all checks passed
|
||||
err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// from this point onward we do not cancel this order.
|
||||
|
||||
// if no error is returned, remove the cart. This should be smooth
|
||||
err = s.cartsRepo.RemoveCart(user_id, cart_id)
|
||||
if err != nil {
|
||||
// Log error but don't fail placing order
|
||||
_ = err
|
||||
}
|
||||
|
||||
// send email to admin
|
||||
go func(user_id uint) {
|
||||
err := s.emailService.SendNewOrderPlacedNotification(user_id)
|
||||
if err != nil {
|
||||
// Log error but don't fail placing order
|
||||
_ = err
|
||||
}
|
||||
}(user_id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderService) ChangeOrderAddress(user *model.Customer, order_id uint, country_id uint, address_info string) error {
|
||||
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !user.HasPermission(perms.OrdersModifyAll) {
|
||||
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return responseErrors.ErrUserHasNoSuchOrder
|
||||
}
|
||||
}
|
||||
|
||||
return s.ordersRepo.ChangeOrderAddress(order_id, country_id, address_info)
|
||||
}
|
||||
|
||||
// This is obiously just an initial version of this function
|
||||
func (s *OrderService) ChangeOrderStatus(user *model.Customer, order_id uint, status string) error {
|
||||
if !user.HasPermission(perms.OrdersModifyAll) {
|
||||
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return responseErrors.ErrUserHasNoSuchOrder
|
||||
}
|
||||
}
|
||||
|
||||
return s.ordersRepo.ChangeOrderStatus(order_id, status)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
"github.com/dlclark/regexp2"
|
||||
"golang.org/x/text/runes"
|
||||
@@ -22,7 +23,7 @@ func SanitizeSlug(s string) string {
|
||||
s = strings.TrimSpace(strings.ToLower(s))
|
||||
|
||||
// First apply explicit transliteration for language-specific letters.
|
||||
s = transliterateWithTable(s)
|
||||
s = transliterateSlug(s)
|
||||
|
||||
// Then normalize and strip any remaining combining marks.
|
||||
s = removeDiacritics(s)
|
||||
@@ -40,19 +41,17 @@ func SanitizeSlug(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func transliterateWithTable(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
func transliterateSlug(s string) string {
|
||||
var cleared string
|
||||
|
||||
for _, r := range s {
|
||||
if repl, ok := constdata.TRANSLITERATION_TABLE[r]; ok {
|
||||
b.WriteString(repl)
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
err := db.DB.Raw("SELECT slugify_eu(?)", s).Scan(&cleared).Error
|
||||
if err != nil {
|
||||
// log error
|
||||
_ = err
|
||||
return s
|
||||
}
|
||||
|
||||
return b.String()
|
||||
return cleared
|
||||
}
|
||||
|
||||
func removeDiacritics(s string) string {
|
||||
|
||||
Reference in New Issue
Block a user