minor rework and filter dimensions
This commit is contained in:
259
cmd/cli/commands/exports.go
Normal file
259
cmd/cli/commands/exports.go
Normal file
@ -0,0 +1,259 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.loyso.art/frx/eway/cmd/cli/components"
|
||||
"git.loyso.art/frx/eway/internal/entity"
|
||||
"git.loyso.art/frx/eway/internal/export"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func ExportCommandTree() *cli.Command {
|
||||
var h exportHandlers
|
||||
return &cli.Command{
|
||||
Name: "export",
|
||||
Usage: "Provide actions to export data from the database",
|
||||
Commands: []*cli.Command{
|
||||
newExportYMLCatalogCommand(h),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newExportYMLCatalogCommand(h exportHandlers) *cli.Command {
|
||||
cmd := cli.Command{
|
||||
Name: "yml-catalog",
|
||||
Usage: "Export data as yml_catalog",
|
||||
}
|
||||
|
||||
return cmdWithAction(cmd, h.YMLCatalog)
|
||||
}
|
||||
|
||||
type exportHandlers struct{}
|
||||
|
||||
func (h exportHandlers) YMLCatalog(ctx context.Context, cmd *cli.Command) error {
|
||||
const defaultCurrency = "RUR"
|
||||
|
||||
log := zerolog.Ctx(ctx)
|
||||
|
||||
path := cmd.String("out")
|
||||
limit := cmd.Int("limit")
|
||||
pretty := cmd.Bool("pretty")
|
||||
|
||||
r, err := components.GetRepository()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting repository: %w", err)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("path", path).
|
||||
Int64("limit", limit).
|
||||
Bool("pretty", pretty).
|
||||
Msg("executing with parameters")
|
||||
|
||||
categories, err := r.Category().List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing categories: %w", err)
|
||||
}
|
||||
|
||||
shop := export.Shop{
|
||||
Currencies: []export.Currency{{
|
||||
ID: defaultCurrency,
|
||||
Rate: 1,
|
||||
}},
|
||||
Categories: make([]export.Category, 0, len(categories)),
|
||||
}
|
||||
|
||||
categoryByNameIdx := make(map[string]int64, len(categories))
|
||||
for _, category := range categories {
|
||||
categoryByNameIdx[category.Name] = category.ID
|
||||
shop.Categories = append(shop.Categories, export.Category{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
})
|
||||
}
|
||||
|
||||
matcher := makeDefaultDimensionDispatcher()
|
||||
|
||||
var skipped int
|
||||
iter := getItemsIter(ctx, r.GoodsItem())
|
||||
for iter.Next() {
|
||||
item := iter.Get()
|
||||
sublog := log.With().Int64("cart", item.Cart).Logger()
|
||||
if item.Sizes == (entity.GoodsItemSize{}) {
|
||||
sublog.Warn().Str("sku", item.Articul).Int64("cart", item.Cart).Msg("skipping item because it does not have size")
|
||||
skipped++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
offer := h.goodsItemAsOffer(iter.Get(), categoryByNameIdx, matcher, sublog)
|
||||
shop.Offers = append(shop.Offers, offer)
|
||||
}
|
||||
if err = iter.Err(); err != nil {
|
||||
return fmt.Errorf("iterating over items: %w", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
errClose := f.Close()
|
||||
if err == nil {
|
||||
err = errClose
|
||||
return
|
||||
}
|
||||
|
||||
log.Err(errClose).Msg("file closed or not")
|
||||
}()
|
||||
|
||||
if limit > 0 {
|
||||
shop.Offers = shop.Offers[:limit]
|
||||
}
|
||||
|
||||
container := export.YmlContainer{
|
||||
YmlCatalog: export.YmlCatalog{
|
||||
Shop: shop,
|
||||
Date: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = f.Write([]byte(xml.Header))
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing header: %w", err)
|
||||
}
|
||||
_, err = f.Write([]byte("<!DOCTYPE yml_catalog SYSTEM \"shops.dtd\">\n"))
|
||||
enc := xml.NewEncoder(f)
|
||||
if pretty {
|
||||
enc.Indent("", " ")
|
||||
}
|
||||
|
||||
log.Info().Int("processed", len(shop.Offers)).Int("skipped", skipped).Msg("completed")
|
||||
|
||||
return enc.Encode(container)
|
||||
}
|
||||
|
||||
func (h exportHandlers) goodsItemAsOffer(in entity.GoodsItem, categoryIDByName map[string]int64, d dimensionDispatcher, log zerolog.Logger) (out export.Offer) {
|
||||
const defaultType = "vendor.model"
|
||||
const defaultCurrency = "RUR"
|
||||
const defaultAvailable = true
|
||||
const quantityParamName = "Количество на складе «Москва»"
|
||||
const basePictureURL = "https://eway.elevel.ru"
|
||||
|
||||
imgurl := func(path string) string {
|
||||
return basePictureURL + path
|
||||
}
|
||||
|
||||
categoryID := categoryIDByName[in.Type]
|
||||
|
||||
pictureURLs := make([]string, 0, len(in.PhotoURLs))
|
||||
for _, url := range in.PhotoURLs {
|
||||
pictureURLs = append(pictureURLs, imgurl(url))
|
||||
}
|
||||
params := make([]export.Param, len(in.Parameters))
|
||||
for k, v := range in.Parameters {
|
||||
if k == "" || v == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if d.isDimensionParam(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
params = append(params, export.Param{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
params = append(params, export.Param{
|
||||
Name: quantityParamName,
|
||||
Value: strconv.Itoa(in.Stock),
|
||||
})
|
||||
|
||||
dimensions := h.formatSizeAsDimensions(in.Sizes, log)
|
||||
out = export.Offer{
|
||||
ID: in.Cart,
|
||||
VendorCode: in.Articul,
|
||||
Price: int(in.TariffPrice),
|
||||
Model: in.Name,
|
||||
Vendor: in.Producer,
|
||||
TypePrefix: in.Name,
|
||||
Description: in.Description,
|
||||
Dimensions: dimensions,
|
||||
|
||||
CategoryID: categoryID,
|
||||
PictureURLs: pictureURLs,
|
||||
Params: params,
|
||||
|
||||
Type: defaultType,
|
||||
CurrencyID: defaultCurrency,
|
||||
Available: defaultAvailable,
|
||||
ManufacturerWarrany: true,
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (exportHandlers) formatSizeAsDimensions(size entity.GoodsItemSize, log zerolog.Logger) string {
|
||||
const delimeter = "/"
|
||||
makeFloat := func(d entity.Dimension) string {
|
||||
value := size.Length.AdjustTo(entity.DimensionKindCentimeter).Value
|
||||
value = float64(int(value*1000)) / 1000.0
|
||||
|
||||
return strconv.FormatFloat(value, 'f', 8, 64)
|
||||
}
|
||||
|
||||
knownSizes := make([]entity.Dimension, 0, 3)
|
||||
|
||||
for _, d := range []entity.Dimension{
|
||||
size.Length,
|
||||
size.Width,
|
||||
size.Height,
|
||||
} {
|
||||
if d.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
knownSizes = append(knownSizes, d)
|
||||
}
|
||||
|
||||
l := makeFloat(size.Length)
|
||||
w := makeFloat(size.Width)
|
||||
h := makeFloat(size.Height)
|
||||
switch len(knownSizes) {
|
||||
case 3:
|
||||
// go on
|
||||
case 2:
|
||||
var side string
|
||||
unknownDefaultSize := makeFloat(entity.NewCentimeterDimensionOrEmpty(30))
|
||||
switch {
|
||||
case size.Length.IsZero():
|
||||
side = "length"
|
||||
l = unknownDefaultSize
|
||||
case size.Width.IsZero():
|
||||
side = "width"
|
||||
w = unknownDefaultSize
|
||||
case size.Height.IsZero():
|
||||
side = "height"
|
||||
h = unknownDefaultSize
|
||||
}
|
||||
log.Warn().Str("size", side).Msg("setting to default value")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
// Output should be the following format:
|
||||
// length/width/height in centimeters
|
||||
return strings.Join([]string{
|
||||
l, w, h,
|
||||
}, delimeter)
|
||||
}
|
||||
Reference in New Issue
Block a user