cdek 0.3.11 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c67295d1a6a2b9199d0c751a230ab6dc48ecda34fe855ec74eca1276efccf05
4
- data.tar.gz: f43d4696e5f063d284329a2fb34fed6f9775807a2a79490623b7f1b6d0ef7526
3
+ metadata.gz: ad8868fd8f6e296c1439f90772bf3c16e724d551ed095d6627250b8182bd486c
4
+ data.tar.gz: 29cabd18b3071e083f7ff8964bf8005f218da9985e35f6bcc80f030ac2677924
5
5
  SHA512:
6
- metadata.gz: 6ffcc0ddbfc9046c56bcc24246922a8755b492f8932d267298602dd7a49bcc447c88f2400ae562214759ff52441855889f67dd9508b7e56e50a7e4cdf11666c1
7
- data.tar.gz: d6b4f68d0f9d992fb22b34f6c31298a0394b08072ae4165414f735d7586dff05e36d54b06e86cd65fd68040d62f8742109780a8fbad2d9c4a2a67a22edf9d4aa
6
+ metadata.gz: bb520764c8df5cfa6b6b012d5e28e219c0a664124caaa07f58ce46ed7a56d079290d7bb8bf1e7f27c02805fd7faca35b051b4df60e3fb94a40112066ad258bf8
7
+ data.tar.gz: 9f832dae5fbb1443e52a9fb4f621b3a8052d308cc88d05d08089e6f2e423f27c815f4e9ecaac3f1c153ce828e470ee5947d528175b4d83c20f7b70eb05bd71ee
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.13
4
+
5
+ * Виджет ПВЗ больше не откатывает центр карты на Москву при выборе
6
+ не-московского города. `defaultLocation` передавался строкой-названием,
7
+ центр ставился асинхронным геокодингом и при сбое оставался дефолт
8
+ [37.62,55.75]. `CityResolver` теперь из одного кэшированного
9
+ `/location/cities` отдаёт и `code`, и координаты `[lng,lat]`;
10
+ `cdek_widget_tag` прокидывает `default_coords`, JS-контроллер использует
11
+ синхронную ветку без геокодинга. При отсутствии координат — fallback на
12
+ строку (нулевая регрессия). Перегенерировать контроллер:
13
+ `bin/rails generate cdek:install`.
14
+
3
15
  ## 0.3.9
4
16
 
5
17
  * В `cdek.gemspec` добавлена development-зависимость `rake`, потому что
@@ -51,6 +51,7 @@ module Cdek
51
51
  data = cdek_widget_data(
52
52
  api_key: api_key.presence || ENV["YANDEX_MAPS_API_KEY"].to_s,
53
53
  default_city: default_city,
54
+ default_coords: cdek_default_coords(default_city),
54
55
  sender_city: sender_city,
55
56
  sender_city_code: sender_city_code.to_s,
56
57
  goods_json: JSON.generate(goods_payload),
@@ -91,7 +92,7 @@ module Cdek
91
92
 
92
93
  # Хэш data-* атрибутов для Stimulus-контроллера cdek-widget.
93
94
  # Ключи в snake_case — Rails сам конвертит "_" в "-" в HTML.
94
- def cdek_widget_data(api_key:, default_city:, sender_city:, sender_city_code:,
95
+ def cdek_widget_data(api_key:, default_city:, default_coords:, sender_city:, sender_city_code:,
95
96
  goods_json:,
96
97
  service_path:, script_url:, modal_id:,
97
98
  field_code:, field_name:, field_address:, field_city_code:,
@@ -102,6 +103,7 @@ module Cdek
102
103
  cdek_widget_script_url_value: script_url,
103
104
  cdek_widget_api_key_value: api_key,
104
105
  cdek_widget_default_location_value: default_city,
106
+ cdek_widget_default_coords_value: default_coords,
105
107
  cdek_widget_sender_city_value: sender_city,
106
108
  cdek_widget_sender_city_code_value: sender_city_code,
107
109
  cdek_widget_goods_value: goods_json,
@@ -115,6 +117,21 @@ module Cdek
115
117
  }
116
118
  end
117
119
 
120
+ # Координаты города «по умолчанию» строкой "долгота,широта" для data-атрибута
121
+ # (или "" если город пуст / не нашёлся / CDEK недоступен). JS-контроллер
122
+ # передаёт их виджету как defaultLocation = [lng, lat]; при пустой строке
123
+ # он откатывается на defaultLocation = название города (прежнее поведение).
124
+ def cdek_default_coords(city_name)
125
+ coords =
126
+ begin
127
+ Cdek.city_coordinates(city_name)
128
+ rescue Cdek::Error
129
+ nil
130
+ end
131
+
132
+ coords.is_a?(Array) && coords.length == 2 ? coords.join(",") : ""
133
+ end
134
+
118
135
  # Путь до UMD-бандла виджета, вшитого в гем. Используется JS-контроллером
119
136
  # для динамической загрузки скрипта по требованию.
120
137
  def cdek_widget_asset_path
@@ -1,18 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cdek
4
- # Resolves a human-readable city name to a CDEK city code.
4
+ # Resolves a human-readable city name to a CDEK city record — both the CDEK
5
+ # city code and the city coordinates — in one API-backed, cached lookup.
5
6
  #
6
7
  # This is intentionally small and API-backed: host applications can pass
7
8
  # user-entered city names to the widget proxy without duplicating lookup
8
- # logic or loading all delivery points for the whole country.
9
+ # logic or loading all delivery points for the whole country. The widget
10
+ # proxy uses #code to filter offices, while the widget helper uses
11
+ # #coordinates to center the map on the chosen city (passing coordinates to
12
+ # the JS widget instead of a name avoids a flaky async geocode that could
13
+ # otherwise drop the map back to its built-in default center).
9
14
  class CityResolver
10
15
  DEFAULT_COUNTRY_CODES = "RU"
11
16
  CACHE_EXPIRES_IN = 43_200
12
17
 
13
18
  class << self
14
19
  def call(name, **options)
15
- new(name, **options).call
20
+ new(name, **options).code
21
+ end
22
+
23
+ def coordinates(name, **options)
24
+ new(name, **options).coordinates
16
25
  end
17
26
  end
18
27
 
@@ -23,27 +32,45 @@ module Cdek
23
32
  @cache = cache
24
33
  end
25
34
 
26
- def call
27
- normalized_name.empty? ? nil : cached_city_code
35
+ # CDEK city code or nil when the city can't be resolved.
36
+ def code
37
+ city = cached_city
38
+ city.is_a?(Hash) ? (city["code"] || city[:code]) : nil
39
+ end
40
+
41
+ # [longitude, latitude] (floats) or nil when coordinates are unknown.
42
+ # The order matches what the CDEK JS widget expects ([lng, lat]).
43
+ def coordinates
44
+ city = cached_city
45
+ lng = city_coordinate(city, "longitude")
46
+ lat = city_coordinate(city, "latitude")
47
+
48
+ (lng && lat) ? [lng, lat] : nil
28
49
  end
29
50
 
30
51
  private
31
52
 
32
53
  attr_reader :name, :client, :country_codes, :cache
33
54
 
34
- def cached_city_code
35
- if cache
36
- cache.fetch(cache_key, expires_in: CACHE_EXPIRES_IN) { fetch_city_code }
55
+ def cached_city
56
+ if normalized_name.empty?
57
+ nil
58
+ elsif cache
59
+ cache.fetch(cache_key, expires_in: CACHE_EXPIRES_IN) { fetch_city }
37
60
  else
38
- fetch_city_code
61
+ fetch_city
39
62
  end
40
63
  end
41
64
 
42
- def fetch_city_code
43
- city = Cdek.locations(client).find_city(normalized_name, country_codes: country_codes)
65
+ def fetch_city
66
+ Cdek.locations(client).find_city(normalized_name, country_codes: country_codes)
67
+ end
44
68
 
69
+ def city_coordinate(city, key)
45
70
  if city.is_a?(Hash)
46
- city["code"] || city[:code]
71
+ raw = city.key?(key) ? city[key] : city[key.to_sym]
72
+ number = Float(raw, exception: false)
73
+ number&.finite? ? number : nil
47
74
  end
48
75
  end
49
76
 
@@ -52,7 +79,7 @@ module Cdek
52
79
  end
53
80
 
54
81
  def cache_key
55
- ["cdek", "city_code", country_codes, normalized_name.downcase].join(":")
82
+ ["cdek", "city", country_codes, normalized_name.downcase].join(":")
56
83
  end
57
84
 
58
85
  def default_cache
@@ -41,7 +41,8 @@ module Cdek
41
41
  def suggestion_payload(city)
42
42
  if city.is_a?(Hash)
43
43
  code = city["code"] || city[:code]
44
- city_name = city["city"] || city[:city] || city["name"] || city[:name]
44
+ full_name = city["full_name"] || city[:full_name]
45
+ city_name = city["city"] || city[:city] || city["name"] || city[:name] || city_name_from_full_name(full_name)
45
46
  region = city["region"] || city[:region]
46
47
  country = city["country"] || city[:country]
47
48
  country_code_value = city["country_code"] || city[:country_code]
@@ -50,17 +51,30 @@ module Cdek
50
51
  {
51
52
  code: code,
52
53
  city: city_name,
54
+ full_name: full_name,
53
55
  region: region,
54
56
  country: country,
55
57
  country_code: country_code_value,
56
- label: label_for(city_name, region)
58
+ label: label_for(city_name, region, full_name)
57
59
  }
58
60
  end
59
61
  end
60
62
  end
61
63
 
62
- def label_for(city_name, region)
63
- [city_name, region].compact.map(&:to_s).reject(&:empty?).uniq.join(", ")
64
+ def city_name_from_full_name(full_name)
65
+ value = full_name.to_s.split(",").first.to_s.strip
66
+
67
+ unless value.empty?
68
+ value
69
+ end
70
+ end
71
+
72
+ def label_for(city_name, region, full_name)
73
+ if full_name.to_s.strip.empty?
74
+ [city_name, region].compact.map(&:to_s).reject(&:empty?).uniq.join(", ")
75
+ else
76
+ full_name.to_s.strip
77
+ end
64
78
  end
65
79
 
66
80
  def normalized_query
data/lib/cdek/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cdek
4
- VERSION = "0.3.11"
4
+ VERSION = "0.3.13"
5
5
  end
data/lib/cdek.rb CHANGED
@@ -68,6 +68,13 @@ module Cdek
68
68
  CityResolver.call(name, **options)
69
69
  end
70
70
 
71
+ # Координаты [долгота, широта] города по пользовательскому названию
72
+ # (или nil, если город/координаты не нашлись). Нужны виджету, чтобы
73
+ # центрировать карту по выбранному городу координатами, а не названием.
74
+ def city_coordinates(name, **options)
75
+ CityResolver.coordinates(name, **options)
76
+ end
77
+
71
78
  # Нормализованные подсказки городов для autocomplete в хост-приложении.
72
79
  def city_suggestions(query, **options)
73
80
  CitySuggestions.call(query, **options)
@@ -77,6 +77,7 @@ export default class extends Controller {
77
77
  scriptUrl: String,
78
78
  apiKey: String,
79
79
  defaultLocation: { type: String, default: "Москва" },
80
+ defaultCoords: { type: String, default: "" },
80
81
  senderCity: { type: String, default: "Москва" },
81
82
  senderCityCode: { type: String, default: "" },
82
83
  goods: { type: String, default: "" },
@@ -138,6 +139,22 @@ export default class extends Controller {
138
139
  return url.pathname + url.search
139
140
  }
140
141
 
142
+ // Координаты выбранного города ([lng, lat]) приходят из data-cdek-widget-default-coords-value.
143
+ // Передаём их виджету как defaultLocation — тогда он центрирует карту по
144
+ // координатам синхронно и не делает асинхронный геокодинг названия, который
145
+ // при сбое оставляет карту на встроенном дефолтном центре (Москве). Если
146
+ // координат нет (город не нашёлся / пусто) — откатываемся на название города.
147
+ _resolveDefaultLocation() {
148
+ const raw = this.defaultCoordsValue.trim()
149
+ const parts = raw === "" ? [] : raw.split(",")
150
+ const lng = Number(parts[0])
151
+ const lat = Number(parts[1])
152
+
153
+ return (parts.length === 2 && Number.isFinite(lng) && Number.isFinite(lat))
154
+ ? [lng, lat]
155
+ : this.defaultLocationValue
156
+ }
157
+
141
158
  _mountWidget() {
142
159
  if (!this.hasRootTarget) return
143
160
  if (typeof window.CDEKWidget !== "function") {
@@ -164,7 +181,7 @@ export default class extends Controller {
164
181
  root: this.rootTarget.id,
165
182
  servicePath: this._servicePath(),
166
183
  apiKey: this.apiKeyValue,
167
- defaultLocation: this.defaultLocationValue,
184
+ defaultLocation: this._resolveDefaultLocation(),
168
185
  from: from,
169
186
  hideDeliveryOptions: { door: true, office: false },
170
187
  lang: "rus",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cdek
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.11
4
+ version: 0.3.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Korolyov