diff --git a/src/webparts/requestPortal/forms/BusinessTrip/README.md b/src/webparts/requestPortal/forms/BusinessTrip/README.md index 0af6c2b..22e5885 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/README.md +++ b/src/webparts/requestPortal/forms/BusinessTrip/README.md @@ -18,10 +18,26 @@ ## Особое поведение - **Можно подать за другого сотрудника.** По умолчанию — сам инициатор; при выборе другого подтягиваются его должность и организации (`Employee/ByEmail`, `useTripEmployee`). +- **Командирующая организация** (с 10-23, логика — `tripCompany.ts`). Сотрудник может работать + в нескольких компаниях — выбирают, от какой он едет (`Select` «Компания — должность»). + Одна организация — выбирается сама, поле заблокировано. Несколько — обязательна при отправке, + должность пустая до выбора и подставляется из выбранной компании. Сотрудника нет в + справочнике — поля нет, отправку не блокирует. Смена сотрудника сбрасывает выбор. Хранение: + `TripCompany` (название) + `TripCompanyId` (CompanyId — под будущий маршрут по компании); + `Organization` — по-прежнему все организации сотрудника. Заявки до 10-23 в просмотре + показывают прежний список организаций. - **Маршрут — лента точек** (`components/route`, логика — `route.ts`; гибрид «маршрут + смета» по образцу Concur/Navan/TripIt). Город отправления → точки (город + объект из `/Project` + даты прибытия/отъезда) → (возвращение — переключатель). Простая поездка — одна точка; «+ Добавить точку маршрута» — для поездок по нескольким объектам. + - **Города — из справочника КАТО** (`shared/dictionary/kato`, поле `CityPicker`): 178 городов + и посёлков РК из официального КАТО (stat.gov.kz), сгенерированы `scripts/Build-KatoCities.ps1`. + Поиск по русскому и казахскому названию с учётом раскладки (Құлсары = Кулсары = Кульсары); + набранное целиком однозначное название подставляется само. Выбор сохраняет оба названия и + код КАТО (`city`/`cityKz`/`cityKato`, у города отправления — `origin`/`originKz`/`originKato`, + всё внутри `TripRouteJson`), показывается везде как «Кульсары (Құлсары)» (`cityLabel`), в том + числе в `TripCity`/`TripRoute` и заголовке. Нет в справочнике (село, заграница) — сохраняется + введённый текст без казахского названия. - Участки пути не хранятся, а выводятся из точек (`routeLegs`, ключ `откуда>куда`); у каждого участка — вид транспорта плашками (`legTransport`, `TRANSPORT_TYPES`), обязателен. - Объект точки — только выбор из справочника (`projectId`): по объектам разносят затраты. @@ -76,7 +92,7 @@ заголовком (см. грабли коллизий internalName). ## Секции формы -EmployeeSection (сотрудник · должность · дата подачи · организация) → RouteSection (маршрут +EmployeeSection (сотрудник · должность · дата подачи · командирующая организация) → RouteSection (маршрут лентой + цель) → TasksSection (задачи) → EstimateSection (смета + итог) → ObligationSection (текст обязательства + согласие). diff --git a/src/webparts/requestPortal/forms/BusinessTrip/components/estimate/PerDiemFields.tsx b/src/webparts/requestPortal/forms/BusinessTrip/components/estimate/PerDiemFields.tsx index 73a3715..76bda01 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/components/estimate/PerDiemFields.tsx +++ b/src/webparts/requestPortal/forms/BusinessTrip/components/estimate/PerDiemFields.tsx @@ -3,6 +3,7 @@ import styles from './PerDiemFields.module.scss'; import { FormField, MoneyInput } from '../../../../shared/ui'; import { IEstimateItem, IRouteStop, MRP_VALUE, MRP_YEAR } from '../../model'; import { formatAmount, itemAmount } from '../../estimate'; +import { stopCityLabel } from '../../route'; import { fieldKey } from '../../validation'; interface Props { @@ -23,7 +24,7 @@ export const PerDiemFields: React.FC = ({ item, stop, onChange }) => { return (
- {stop?.city.trim() || 'Точка маршрута'} + {(stop && stopCityLabel(stop)) || 'Точка маршрута'} Проект: {project || 'не выбран'}
diff --git a/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteEditor.tsx b/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteEditor.tsx index c3af63f..12be714 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteEditor.tsx +++ b/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteEditor.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import type { WebPartContext } from '@microsoft/sp-webpart-base'; import styles from './RouteEditor.module.scss'; -import { FormField, Icon, TextInput, Toggle, useFormReadOnly } from '../../../../shared/ui'; -import { IRouteStop, ITripRoute } from '../../model'; -import { IRouteLeg, createStop, routeLegs } from '../../route'; +import { FormField, Icon, Toggle, useFormReadOnly } from '../../../../shared/ui'; +import { CityPicker } from '../../../../shared/dictionary'; +import { CITY_PLACEHOLDER, IRouteStop, ITripRoute } from '../../model'; +import { IRouteLeg, createStop, originLabel, routeLegs } from '../../route'; import { fieldKey } from '../../validation'; import { RouteStopCard } from './RouteStopCard'; import { RouteLegRow } from './RouteLegRow'; @@ -71,11 +72,11 @@ export const RouteEditor: React.FC = ({ route, onChange, context }) => {
- onChange({ ...route, origin: v })} - placeholder="Напр.: Алматы" + value={{ name: route.origin, nameKz: route.originKz, kato: route.originKato }} + onChange={(city) => onChange({ ...route, origin: city.name, originKz: city.nameKz, originKato: city.kato })} + placeholder={CITY_PLACEHOLDER} />
@@ -111,13 +112,13 @@ export const RouteEditor: React.FC = ({ route, onChange, context }) => {
{readOnly ? ( - {route.returnToOrigin ? `Возвращение: ${route.origin || 'город отправления'}` : 'Без возвращения в город отправления'} + {route.returnToOrigin ? `Возвращение: ${originLabel(route) || 'город отправления'}` : 'Без возвращения в город отправления'} ) : ( onChange({ ...route, returnToOrigin })} - onLabel={`Возвращение в ${route.origin.trim() || 'город отправления'}`} + onLabel={`Возвращение в ${originLabel(route) || 'город отправления'}`} /> )}
diff --git a/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteScheme.tsx b/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteScheme.tsx index 2307e8d..80510d2 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteScheme.tsx +++ b/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteScheme.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import styles from './RouteScheme.module.scss'; import { IEstimateArticle, ITripRoute } from '../../model'; -import { routeLegs, routePeriod, routeProjects } from '../../route'; +import { originLabel, routeLegs, routePeriod, routeProjects, stopCityLabel } from '../../route'; import { estimateTotal, formatAmount, stopTotals } from '../../estimate'; import { plural, tripDays } from '../../tripData'; import { ISchemeNode } from './schemeNode'; @@ -18,14 +18,14 @@ interface Props { /** Узлы схемы по порядку: город отправления → точки → (возвращение). */ function schemeNodes(route: ITripRoute, amounts: Map): ISchemeNode[] { const period = routePeriod(route); - const origin = route.origin.trim(); + const origin = originLabel(route); const withDate = (caption: string, iso: string): string => (iso ? `${caption} ${shortDate(iso)}` : caption); const nodes: ISchemeNode[] = [ { key: 'origin', kind: 'endpoint', city: origin, caption: withDate('выезд', period.start), amount: 0 } ]; route.stops.forEach((stop, i) => - nodes.push({ key: stop.id, kind: 'stop', number: i + 1, stop, city: stop.city.trim(), amount: amounts.get(stop.id) || 0 }) + nodes.push({ key: stop.id, kind: 'stop', number: i + 1, stop, city: stopCityLabel(stop), amount: amounts.get(stop.id) || 0 }) ); if (route.returnToOrigin) { nodes.push({ key: 'return', kind: 'endpoint', city: origin, caption: withDate('возвращение', period.end), amount: 0 }); diff --git a/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteStopCard.tsx b/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteStopCard.tsx index 03a78a9..80a8d27 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteStopCard.tsx +++ b/src/webparts/requestPortal/forms/BusinessTrip/components/route/RouteStopCard.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import type { WebPartContext } from '@microsoft/sp-webpart-base'; import styles from './RouteStopCard.module.scss'; import { FormField, Icon, TextInput } from '../../../../shared/ui'; -import { ProjectPicker } from '../../../../shared/dictionary'; -import { IRouteStop, PROJECT_PLACEHOLDER } from '../../model'; +import { CityPicker, ProjectPicker } from '../../../../shared/dictionary'; +import { CITY_PLACEHOLDER, IRouteStop, PROJECT_PLACEHOLDER } from '../../model'; import { stopNights } from '../../route'; import { plural } from '../../tripData'; import { fieldKey } from '../../validation'; @@ -41,7 +41,12 @@ export const RouteStopCard: React.FC = ({ stop, index, context, onChange,
- onChange({ city: v })} /> + onChange({ city: city.name, cityKz: city.nameKz, cityKato: city.kato })} + placeholder={CITY_PLACEHOLDER} + /> = ({ node }) => { const dates = stopDates(stop); return ( - {stop.city.trim() || `Город ${NOT_SET}`} + {node.city || `Город ${NOT_SET}`} {/* Названия объектов в справочнике бывают с полным адресом — режем до 2 строк, полное — в подсказке. */} {stop.project.trim() || 'объект не выбран'} diff --git a/src/webparts/requestPortal/forms/BusinessTrip/components/route/schemeNode.ts b/src/webparts/requestPortal/forms/BusinessTrip/components/route/schemeNode.ts index c2a1370..84f6420 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/components/route/schemeNode.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/components/route/schemeNode.ts @@ -8,7 +8,7 @@ export interface ISchemeNode { number?: number; /** Сама точка — только у точки маршрута. */ stop?: IRouteStop; - /** Город отправления — у endpoint. */ + /** Город на двух языках (см. route.ts/stopCityLabel): отправления — у endpoint, точки — у stop. */ city: string; /** «выезд 02.10» / «возвращение 05.10» — у endpoint. */ caption?: string; diff --git a/src/webparts/requestPortal/forms/BusinessTrip/estimate.test.ts b/src/webparts/requestPortal/forms/BusinessTrip/estimate.test.ts index c576a8c..749de8f 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/estimate.test.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/estimate.test.ts @@ -17,9 +17,11 @@ import { const ROUTE: ITripRoute = { origin: 'Алматы', + originKz: '', + originKato: '', stops: [ - { id: 's1', city: 'Атырау', project: 'Нефтебаза-2', projectId: 'p1', dateFrom: '2026-10-05', dateTo: '2026-10-08' }, - { id: 's2', city: 'Актау', project: 'Порт Курык', projectId: 'p2', dateFrom: '2026-10-08', dateTo: '2026-10-10' } + { id: 's1', city: 'Атырау', cityKz: '', cityKato: '', project: 'Нефтебаза-2', projectId: 'p1', dateFrom: '2026-10-05', dateTo: '2026-10-08' }, + { id: 's2', city: 'Актау', cityKz: '', cityKato: '', project: 'Порт Курык', projectId: 'p2', dateFrom: '2026-10-08', dateTo: '2026-10-10' } ], returnToOrigin: true, legTransport: {} diff --git a/src/webparts/requestPortal/forms/BusinessTrip/model.ts b/src/webparts/requestPortal/forms/BusinessTrip/model.ts index ecb474e..899ebf1 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/model.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/model.ts @@ -20,7 +20,12 @@ export const FORM_TITLE = 'Заявка на командировку'; export interface IRouteStop { /** Стабильный ключ точки: на него ссылаются позиции сметы (IEstimateItem.linkId). */ id: string; + /** Город на русском — из справочника КАТО (shared/dictionary/kato) или введён вручную. */ city: string; + /** Город на казахском; '' — введён вручную (в справочнике не нашёлся). */ + cityKz: string; + /** Код КАТО города; '' — введён вручную. */ + cityKato: string; /** Объект — проект из справочника /Project (см. shared/dictionary/ProjectPicker). */ project: string; /** oid проекта; '' — текст введён вручную без выбора из списка (отправка не пропустит). */ @@ -38,8 +43,12 @@ export interface IRouteStop { * участка по его ключу. */ export interface ITripRoute { - /** Город отправления (обычно город работы). */ + /** Город отправления (обычно город работы), на русском. */ origin: string; + /** Город отправления на казахском; '' — введён вручную. */ + originKz: string; + /** Код КАТО города отправления; '' — введён вручную. */ + originKato: string; stops: IRouteStop[]; /** Обратный путь из последней точки в город отправления. */ returnToOrigin: boolean; @@ -306,7 +315,9 @@ export const FIRST_STOP_ID = 'stop-1'; export const EMPTY_ROUTE: ITripRoute = { origin: '', - stops: [{ id: FIRST_STOP_ID, city: '', project: '', projectId: '', dateFrom: '', dateTo: '' }], + originKz: '', + originKato: '', + stops: [{ id: FIRST_STOP_ID, city: '', cityKz: '', cityKato: '', project: '', projectId: '', dateFrom: '', dateTo: '' }], returnToOrigin: true, legTransport: {} }; @@ -364,4 +375,5 @@ export const OBLIGATION_TEXT = export const OBLIGATION_LABEL = 'Ознакомлен(а) и согласен(на)'; export const EMPLOYEE_PLACEHOLDER = 'Начните вводить Ф.И.О. сотрудника…'; -export const PROJECT_PLACEHOLDER = 'Начните вводить название или код объекта…'; +export const CITY_PLACEHOLDER = 'Начните вводить город: Атырау, Құлсары…'; +export const PROJECT_PLACEHOLDER ='Начните вводить название или код объекта…'; diff --git a/src/webparts/requestPortal/forms/BusinessTrip/route.test.ts b/src/webparts/requestPortal/forms/BusinessTrip/route.test.ts index 94618e0..a88aeee 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/route.test.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/route.test.ts @@ -14,9 +14,11 @@ import { const ROUTE: ITripRoute = { origin: 'Алматы', + originKz: '', + originKato: '', stops: [ - { id: 's1', city: 'Атырау', project: 'Нефтебаза-2', projectId: 'p1', dateFrom: '2026-10-05', dateTo: '2026-10-08' }, - { id: 's2', city: 'Актау', project: 'Порт Курык', projectId: 'p2', dateFrom: '2026-10-08', dateTo: '2026-10-10' } + { id: 's1', city: 'Атырау', cityKz: '', cityKato: '', project: 'Нефтебаза-2', projectId: 'p1', dateFrom: '2026-10-05', dateTo: '2026-10-08' }, + { id: 's2', city: 'Актау', cityKz: '', cityKato: '', project: 'Порт Курык', projectId: 'p2', dateFrom: '2026-10-08', dateTo: '2026-10-10' } ], returnToOrigin: true, legTransport: { 'origin>s1': 'Авиа' } @@ -48,6 +50,18 @@ describe('сводка маршрута', () => { ); }); + it('город из справочника — на двух языках, если названия различаются', () => { + const route: ITripRoute = { + ...ROUTE, + origin: 'Уральск', + originKz: 'Орал', + stops: [{ ...ROUTE.stops[0], city: 'Кульсары', cityKz: 'Құлсары' }, { ...ROUTE.stops[1], cityKz: 'Актау' }], + returnToOrigin: false + }; + expect(routeCities(route)).toBe('Кульсары (Құлсары), Актау'); + expect(routeLegs(route).map(legLabel)).toEqual(['Уральск (Орал) → Кульсары (Құлсары)', 'Кульсары (Құлсары) → Актау']); + }); + it('новая точка начинается с даты отъезда из предыдущей', () => { expect(createStop(ROUTE.stops[1]).dateFrom).toBe('2026-10-10'); expect(createStop(undefined).dateFrom).toBe(''); @@ -59,6 +73,13 @@ describe('хранение маршрута', () => { expect(parseRoute(serializeRoute(ROUTE))).toEqual(ROUTE); }); + it('заявка до справочника КАТО — город как был, без казахского названия', () => { + const old = { origin: 'Алматы', stops: [{ id: 's1', city: 'Атырау', project: '', projectId: '', dateFrom: '', dateTo: '' }] }; + const route = parseRoute(JSON.stringify(old)); + expect([route.originKz, route.stops[0].cityKz, route.stops[0].cityKato]).toEqual(['', '', '']); + expect(routeCities(route)).toBe('Атырау'); + }); + it('битый JSON или маршрут без точек — маршрут новой заявки', () => { expect(parseRoute('{oops')).toEqual(EMPTY_ROUTE); expect(parseRoute(JSON.stringify({ origin: 'Алматы', stops: [] }))).toEqual(EMPTY_ROUTE); diff --git a/src/webparts/requestPortal/forms/BusinessTrip/route.ts b/src/webparts/requestPortal/forms/BusinessTrip/route.ts index 62eb376..f85daab 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/route.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/route.ts @@ -1,4 +1,6 @@ import { formatIsoDate } from '../../shared/dateUtils'; +// Прямой импорт, не через shared/dictionary: индекс тянет сервисы с sp-http, а route.ts тестируется без SPFx. +import { cityLabel } from '../../shared/dictionary/kato/cityLabel'; import { EMPTY_ROUTE, IRouteStop, ITripRoute, ORIGIN_ID } from './model'; import { tripDays } from './tripData'; @@ -24,8 +26,18 @@ export function newId(prefix: string): string { const ORIGIN_FALLBACK = 'Город отправления'; const STOP_FALLBACK = 'Точка маршрута'; +/** Город точки на двух языках: «Кульсары (Құлсары)»; '' — не указан. */ +export function stopCityLabel(stop: IRouteStop): string { + return cityLabel(stop.city, stop.cityKz); +} + +/** Город отправления на двух языках; '' — не указан. */ +export function originLabel(route: ITripRoute): string { + return cityLabel(route.origin, route.originKz); +} + function stopCity(stop: IRouteStop): string { - return stop.city.trim() || STOP_FALLBACK; + return stopCityLabel(stop) || STOP_FALLBACK; } /** Подпись точки в выпадающих списках сметы: «Атырау · Нефтебаза-2». */ @@ -40,7 +52,7 @@ export function stopLabel(stop: IRouteStop): string { * участки пересобрались сами. */ export function routeLegs(route: ITripRoute): IRouteLeg[] { - const origin = { id: ORIGIN_ID, name: route.origin.trim() || ORIGIN_FALLBACK }; + const origin = { id: ORIGIN_ID, name: originLabel(route) || ORIGIN_FALLBACK }; const points = [origin, ...route.stops.map((s) => ({ id: s.id, name: stopCity(s) }))]; if (route.returnToOrigin) points.push(origin); @@ -91,7 +103,7 @@ const unique = (values: string[]): string[] => /** Города точек через запятую — для колонки TripCity и заголовка элемента. */ export function routeCities(route: ITripRoute): string { - return unique(route.stops.map((s) => s.city)).join(', '); + return unique(route.stops.map(stopCityLabel)).join(', '); } /** Объекты точек (без повторов) — для колонки TripProjects. */ @@ -106,12 +118,12 @@ function stopDates(stop: IRouteStop): string { /** Маршрут читаемым текстом для колонки SP: «Алматы → Атырау (Нефтебаза-2, 05.10.2026–08.10.2026) → Алматы». */ export function routeText(route: ITripRoute): string { - const parts: string[] = [route.origin.trim() || ORIGIN_FALLBACK]; + const parts: string[] = [originLabel(route) || ORIGIN_FALLBACK]; route.stops.forEach((stop) => { const details = [stop.project.trim(), stopDates(stop)].filter(Boolean).join(', '); parts.push(details ? `${stopCity(stop)} (${details})` : stopCity(stop)); }); - if (route.returnToOrigin) parts.push(route.origin.trim() || ORIGIN_FALLBACK); + if (route.returnToOrigin) parts.push(originLabel(route) || ORIGIN_FALLBACK); return parts.join(' → '); } @@ -123,6 +135,8 @@ export function createStop(previous: IRouteStop | undefined): IRouteStop { return { id: newId('stop'), city: '', + cityKz: '', + cityKato: '', project: '', projectId: '', dateFrom: previous?.dateTo || '', @@ -144,6 +158,9 @@ function toStop(value: unknown): IRouteStop | undefined { return { id, city: text(raw.city), + // Заявки до справочника КАТО — без этих полей: город остаётся введённым вручную. + cityKz: text(raw.cityKz), + cityKato: text(raw.cityKato), project: text(raw.project), projectId: text(raw.projectId), dateFrom: text(raw.dateFrom), @@ -177,6 +194,8 @@ export function parseRoute(raw: unknown): ITripRoute { if (stops.length === 0) return EMPTY_ROUTE; return { origin: text(rawRoute.origin), + originKz: text(rawRoute.originKz), + originKato: text(rawRoute.originKato), stops, returnToOrigin: rawRoute.returnToOrigin !== false, legTransport: toTransport(rawRoute.legTransport) diff --git a/src/webparts/requestPortal/forms/BusinessTrip/validation.test.ts b/src/webparts/requestPortal/forms/BusinessTrip/validation.test.ts index aa0ba04..def4d01 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/validation.test.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/validation.test.ts @@ -6,7 +6,9 @@ const off = (article: IEstimateArticle): IEstimateArticle => ({ ...article, enab const ROUTE: ITripRoute = { origin: 'Алматы', - stops: [{ id: 's1', city: 'Атырау', project: 'Нефтебаза-2', projectId: 'p1', dateFrom: '2026-10-05', dateTo: '2026-10-08' }], + originKz: '', + originKato: '', + stops: [{ id: 's1', city: 'Атырау', cityKz: '', cityKato: '', project: 'Нефтебаза-2', projectId: 'p1', dateFrom: '2026-10-05', dateTo: '2026-10-08' }], returnToOrigin: true, legTransport: { 'origin>s1': 'Авиа', 's1>origin': 'Авиа' } }; @@ -64,7 +66,7 @@ describe('validation: маршрут', () => { }); it('прибытие в следующую точку раньше отъезда из предыдущей', () => { - const second = { id: 's2', city: 'Актау', project: 'Порт', projectId: 'p2', dateFrom: '2026-10-06', dateTo: '2026-10-09' }; + const second = { id: 's2', city: 'Актау', cityKz: '', cityKato: '', project: 'Порт', projectId: 'p2', dateFrom: '2026-10-06', dateTo: '2026-10-09' }; const route = { ...ROUTE, stops: [...ROUTE.stops, second], legTransport: { 'origin>s1': 'Авиа', 's1>s2': 'Авиа', 's2>origin': 'Авиа' } }; expect(fields(submitIssues({ ...FILLED, Route: route }))).toEqual(['stop-s2-from']); }); diff --git a/src/webparts/requestPortal/forms/BusinessTrip/validation.ts b/src/webparts/requestPortal/forms/BusinessTrip/validation.ts index 5392ed4..d18cb6f 100644 --- a/src/webparts/requestPortal/forms/BusinessTrip/validation.ts +++ b/src/webparts/requestPortal/forms/BusinessTrip/validation.ts @@ -2,7 +2,7 @@ import type { IFieldIssue } from '../../shared/ui'; import type { IEmployeeOrganization } from '../../shared/dictionary'; import { ESTIMATE_KINDS, IEstimateArticle, IEstimateItem, IFormData, IRouteStop, ITripRoute } from './model'; import { filledTasks } from './tripData'; -import { legLabel, routeLegs } from './route'; +import { legLabel, routeLegs, stopCityLabel } from './route'; import { articleTitle, itemAmount, itemLinkLabel, linkOptions, resolveLinkId } from './estimate'; /** @@ -26,7 +26,8 @@ export const fieldKey = { /** «Точка 2 (Актау)» — как точку видно в форме. */ function stopName(stop: IRouteStop, index: number): string { - return stop.city.trim() ? `Точка ${index + 1} (${stop.city.trim()})` : `Точка ${index + 1}`; + const city = stopCityLabel(stop); + return city ? `Точка ${index + 1} (${city})` : `Точка ${index + 1}`; } /** Дата окончания раньше начала — блокирует даже черновик: такие даты не сохранить осмысленно. */