package marketing import ( "fmt" "time" ) // PresetID identifies a seasonal content-calendar preset. type PresetID string const ( PresetBlackFriday PresetID = "black_friday" PresetChristmas PresetID = "christmas" ) // CampaignStructureKey is stored on export_feeds.template JSON (no migration). const CampaignStructureKey = "_campaign" // Preset is a dated seasonal window for preparing an export feed. type Preset struct { ID PresetID `json:"id"` Name string `json:"name"` Description string `json:"description"` StartDate string `json:"start_date"` EndDate string `json:"end_date"` Year int `json:"year"` } // StructureMeta is persisted under template._campaign. type StructureMeta struct { PresetID PresetID `json:"presetId"` Name string `json:"name"` StartDate string `json:"startDate"` EndDate string `json:"endDate"` Year int `json:"year"` PreparedAt string `json:"preparedAt"` } func pad2(n int) string { return fmt.Sprintf("%02d", n) } func toISODate(t time.Time) string { return fmt.Sprintf("%s-%s-%s", pad2(t.Year()), pad2(int(t.Month())), pad2(t.Day())) } // BlackFridayDate returns Black Friday (day after US Thanksgiving) in UTC date parts. func BlackFridayDate(year int) time.Time { nov1 := time.Date(year, time.November, 1, 0, 0, 0, 0, time.UTC) dow := int(nov1.Weekday()) // Sunday=0 firstThursday := 1 + ((4 - dow + 7) % 7) fourthThursday := firstThursday + 21 return time.Date(year, time.November, fourthThursday+1, 0, 0, 0, 0, time.UTC) } // ResolvePreset returns date window for a preset id and year. func ResolvePreset(id PresetID, year int) (Preset, error) { if year < 2000 || year > 2100 { return Preset{}, ClientMsg("invalid year") } switch id { case PresetBlackFriday: bf := BlackFridayDate(year) start := bf.AddDate(0, 0, -7) end := bf.AddDate(0, 0, 3) return Preset{ ID: id, Name: "Black Friday", Description: "Promo window around Black Friday — prepare a Google Shopping-style export feed.", StartDate: toISODate(start), EndDate: toISODate(end), Year: year, }, nil case PresetChristmas: return Preset{ ID: PresetChristmas, Name: "Christmas", Description: "Holiday catalog push from Dec 1 through Boxing Day.", StartDate: toISODate(time.Date(year, time.December, 1, 0, 0, 0, 0, time.UTC)), EndDate: toISODate(time.Date(year, time.December, 26, 0, 0, 0, 0, time.UTC)), Year: year, }, nil default: return Preset{}, ClientMsg("preset_id must be black_friday or christmas") } } // ListPresets returns Black Friday + Christmas for the year. func ListPresets(year int) []Preset { bf, _ := ResolvePreset(PresetBlackFriday, year) xmas, _ := ResolvePreset(PresetChristmas, year) return []Preset{bf, xmas} } // DefaultCampaignMappings are Google Shopping-ish CSV column → product field sources. func DefaultCampaignMappings() map[string]string { return map[string]string{ "id": "product_id", "title": "processed_name", "description": "processed_description", "link": "attr.url", "image_link": "attr.image", "availability": "attr.availability", "price": "attr.price", "brand": "attr.brand", "gtin": "gtin", "google_product_category": "category", "condition": "attr.condition", } }