From d7f88b661f5bbdd3ae161fbf1c30cc11a68e032a Mon Sep 17 00:00:00 2001 From: ant Date: Tue, 21 Jul 2026 08:44:52 +0000 Subject: [PATCH] =?UTF-8?q?[598-51][@ant]=20fix:=20=D0=BF=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=D0=B7=D0=B0=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D1=83=20=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=B2=D1=89=D0=B8=D0=BA=D1=83=20=D0=BA=D0=BE=D1=80=D1=80?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BD=D1=83=D1=8E=20=D1=81=D1=83=D0=BC=D0=BC?= =?UTF-8?q?=D1=83=20=D1=81=20=D1=87=D0=BB=D0=B5=D0=BD=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=BC=20=D0=B2=D0=B7=D0=BD=D0=BE=D1=81=D0=BE=D0=BC=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=81=D0=B5=D1=85=20=D1=8D=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=D1=85=20=D0=A1=D1=82=D0=BE=D0=BB=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D0=B0=D0=B7=D0=BE=D0=B2,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=82=D0=BE=D1=87=D0=B5=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20UI-=D0=B1=D0=B0=D0=B3=D0=B8=20=D0=B8=20TS-=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B8=20=E2=80=94=20=D1=80=D0=B5=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=80=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=89=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D1=83=D0=BD=D0=BA=D1=82?= =?UTF-8?q?=D1=8B=20=D0=B2=D1=8B=D0=B4=D0=B0=D1=87=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B8=D0=BB=D0=B8=20=D0=A4=D0=98=D0=9E/?= =?UTF-8?q?=D0=BD=D0=B0=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=B3?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B3=D0=BE=20username,=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=BB=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=D0=B0=20=D0=B8=20=D0=BE=D0=BD=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=D0=B4=D0=B8=D0=BD=D0=B3=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D0=BE=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=88=D0=BB=D1=8B=D0=BC=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=D0=BC,=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0=20=D1=83=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B8=20=D0=B2=D1=8B=D0=B4=D0=B0=D1=87=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BF=D1=83=D1=81=D0=BA=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=83=D1=8E=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D1=8C=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0=20=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=B0=D0=B4=D0=B0=D0=BB=D0=B0=20=D0=B1=D1=8B=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B1=D1=8D=D0=BA=D0=B5,=20=D0=BF=D0=BB=D1=8E=D1=81?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D1=8B=20=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=B2=D1=88=D0=B8=D0=B5=D1=81=D1=8F=20unknown/null?= =?UTF-8?q?able=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=B8=D0=BB=D1=8F=D1=86=D0=B8=D0=B8=20desktop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- .../desktop/extensions/market/install.ts | 2 +- .../pages/PostingsPage/ui/PostingsPage.vue | 2 +- .../desktop/src-ssr/middlewares/injectEnv.ts | 1 + components/desktop/src/boot/index.ts | 1 - .../ui/AdminIssuancePointsPage.vue | 22 +++++------- .../AdminOrders/ui/AdminOrdersPage.vue | 9 +++-- .../pages/Marketplace/Cart/ui/CartPage.vue | 14 ++++---- .../MarketplaceCatalog/ui/AddToCartDialog.vue | 27 +++++++++++---- .../ui/MarketplaceCatalogPage.vue | 3 +- .../MarketplaceOfferDetail/api/index.ts | 2 +- .../ui/MarketplaceOfferDetailPage.vue | 26 ++++++++++++-- .../Marketplace/MyOrders/ui/MyOrdersPage.vue | 6 ++-- .../ui/OffererIncomingOrdersPage.vue | 7 +++- .../OffererSupplyPreparation/lib/ttn.ts | 2 +- .../ui/OnboardingMemberPickCppPage.vue | 4 +-- .../ui/OperatorBranchEconomyPage.vue | 2 +- .../ui/OperatorReceptionPage.vue | 4 +-- .../ui/OrdererConsolidatedPage.vue | 3 +- .../ui/OrdererOrderDetailPage.vue | 14 ++++++-- .../ui/SupplierRegistryPage.vue | 3 +- .../Registrator/SignUp/SelectProgram.vue | 2 +- .../desktop/src/shared/config/Environment.ts | 1 + .../src/shared/config/createEnvObject.ts | 1 + .../src/shared/lib/marketplace/index.ts | 1 + .../shared/lib/marketplace/membership-fee.ts | 11 ++++-- .../lib/marketplace/reception-grouping.ts | 4 +-- .../src/shared/ui/Providers/Bank/Bank.vue | 2 +- .../shared/ui/base/BaseInput/BaseInput.vue | 6 ++-- .../CatalogOfferCard/CatalogOfferCard.vue | 3 +- .../model/useOnsiteSignatureGate.ts | 28 +++++++++------ .../Marketplace/OrderCard/OrderCard.vue | 14 ++++++++ .../OrderCard/lib/toOrderCardModel.ts | 22 ++++++++++-- .../StockRestockPanel/StockRestockPanel.vue | 34 +++++++++++++++++-- .../SupplyPartyCard/SupplyPartyCard.vue | 12 +++++++ .../ProcessDetailCard/ProcessDetailCard.vue | 3 +- 35 files changed, 219 insertions(+), 79 deletions(-) diff --git a/components/desktop/extensions/market/install.ts b/components/desktop/extensions/market/install.ts index 48b2d03c912..2d05133975b 100644 --- a/components/desktop/extensions/market/install.ts +++ b/components/desktop/extensions/market/install.ts @@ -708,7 +708,7 @@ export default async function (): Promise { component: markRaw(AdminOffersPage), meta: { title: 'Реестр предложений', - icon: 'storefront', + icon: 'sell', requires: 'Offer:read:all', requiresAuth: true, agreements: agreementsBase, diff --git a/components/desktop/extensions/reports/pages/PostingsPage/ui/PostingsPage.vue b/components/desktop/extensions/reports/pages/PostingsPage/ui/PostingsPage.vue index e680c8625bc..c315b80efce 100644 --- a/components/desktop/extensions/reports/pages/PostingsPage/ui/PostingsPage.vue +++ b/components/desktop/extensions/reports/pages/PostingsPage/ui/PostingsPage.vue @@ -209,7 +209,7 @@ EntityIdBadge( v-if='props.row.processHash' :rawId='shortHash(props.row.processHash)' - @click='copyFullHash(props.row.processHash)' + @click='copyText(props.row.processHash)' ) diff --git a/components/desktop/src-ssr/middlewares/injectEnv.ts b/components/desktop/src-ssr/middlewares/injectEnv.ts index 461a00e3fb2..8d3629bc61c 100644 --- a/components/desktop/src-ssr/middlewares/injectEnv.ts +++ b/components/desktop/src-ssr/middlewares/injectEnv.ts @@ -26,6 +26,7 @@ export default ssrMiddleware(({ app }) => { VAPID_PUBLIC_KEY: process.env.VAPID_PUBLIC_KEY as string, SENTRY_DSN: process.env.SENTRY_DSN as string, OPENREPLAY_PROJECT_KEY: process.env.OPENREPLAY_PROJECT_KEY as string, + YANDEX_MAPS_API_KEY: process.env.YANDEX_MAPS_API_KEY as string, }; // Создаем скрипт, который добавит переменные в window.__ENV__ diff --git a/components/desktop/src/boot/index.ts b/components/desktop/src/boot/index.ts index 0766154b410..1b804666a23 100644 --- a/components/desktop/src/boot/index.ts +++ b/components/desktop/src/boot/index.ts @@ -1,5 +1,4 @@ export { default as init } from './init'; export { default as axios } from './axios'; export { default as sentry } from './sentry'; -export { default as map } from './map'; export { default as chatwoot } from './chatwoot'; diff --git a/components/desktop/src/pages/Marketplace/AdminIssuancePoints/ui/AdminIssuancePointsPage.vue b/components/desktop/src/pages/Marketplace/AdminIssuancePoints/ui/AdminIssuancePointsPage.vue index b03149abeb3..2ff65758ddf 100644 --- a/components/desktop/src/pages/Marketplace/AdminIssuancePoints/ui/AdminIssuancePointsPage.vue +++ b/components/desktop/src/pages/Marketplace/AdminIssuancePoints/ui/AdminIssuancePointsPage.vue @@ -14,7 +14,7 @@ import { GeocodeStatus, KuDetailsStatus } from 'src/entities/MarketplaceKUDetail import type { IMarketplaceKUDetails } from 'src/entities/MarketplaceKUDetails' import { BaseBadge, BaseButton, BaseDialog, EmptyState, TableSkeleton } from 'src/shared/ui/base' import type { BaseBadgeVariant, TableSkeletonColumn } from 'src/shared/ui/base' -import { PageHint } from 'src/shared/ui/domain' +import { IdentityCell, PageHint } from 'src/shared/ui/domain' import { useMarketplaceRealtime } from 'src/shared/lib/marketplace' // Map экспортируется как `Map` — импортируем под алиасом, чтобы не затенять // глобальный `Map` (используется в `rows`). @@ -81,6 +81,10 @@ function statusOf(row: IssuancePointRow): { label: string; variant: BaseBadgeVar return STATUS_LABEL[row.details.status] } +function branchName(row: IssuancePointRow): string { + return row.branch.short_name || row.branch.full_name || '' +} + function addressOf(row: IssuancePointRow): string { return ( row.details?.addressFull || @@ -213,8 +217,10 @@ q-page.admin-pvz tbody tr(v-for='row in rows', :key='row.branch.braname') td.col-ku - .admin-pvz__ku-name {{ row.branch.short_name || row.branch.full_name || row.branch.braname }} - .admin-pvz__ku-acc {{ row.branch.braname }} + IdentityCell( + :account-name='row.branch.braname', + :full-name='branchName(row)' + ) td.col-city {{ row.branch.city || '—' }} td.admin-pvz__address {{ addressOf(row) }} td.col-status @@ -334,16 +340,6 @@ q-page.admin-pvz font-size: 0.875rem; } - &__ku-name { - font-weight: 600; - } - - &__ku-acc { - font-family: var(--font-mono); - font-size: 0.8125rem; - color: var(--p-ink-3); - } - &__address { color: var(--p-ink-2); } diff --git a/components/desktop/src/pages/Marketplace/AdminOrders/ui/AdminOrdersPage.vue b/components/desktop/src/pages/Marketplace/AdminOrders/ui/AdminOrdersPage.vue index 0c57285f42d..f0c8e5069a0 100644 --- a/components/desktop/src/pages/Marketplace/AdminOrders/ui/AdminOrdersPage.vue +++ b/components/desktop/src/pages/Marketplace/AdminOrders/ui/AdminOrdersPage.vue @@ -90,8 +90,11 @@ function shortId(id: string | null | undefined): string { function unitLabel(o: AdminOrderView): string { return marketplaceUnitShort(o.unit_of_measure); } -function formatTotal(v: string | null | undefined): string { - return v ? formatAsset2Digits(String(v)) : '—'; +// requirement b6: реестр показывает сумму, которую реально заплатил пайщик +// (себестоимость + членский взнос, зафиксированный в заказе контрактом) — +// не голую себестоимость товара. +function formatTotalWithFee(o: AdminOrderView): string { + return formatAsset2Digits(o.total_cost_with_fee); } function formatDate(d: unknown): string { if (d === null || d === undefined) return '—'; @@ -255,7 +258,7 @@ q-page.admin-orders(role="region", aria-label="Реестр заказов ко q-td {{ ordererTitle(props.row) }} q-td {{ supplierTitle(props.row) }} q-td.text-right {{ props.row.quantity }} {{ unitLabel(props.row) }} - q-td.text-right.font-monospace {{ formatTotal(props.row.total_cost) }} + q-td.text-right.font-monospace {{ formatTotalWithFee(props.row) }} q-td {{ formatDate(props.row.created_at) }} q-tr.q-virtual-scroll--with-prev( diff --git a/components/desktop/src/pages/Marketplace/Cart/ui/CartPage.vue b/components/desktop/src/pages/Marketplace/Cart/ui/CartPage.vue index 3f41365e492..670deae726f 100644 --- a/components/desktop/src/pages/Marketplace/Cart/ui/CartPage.vue +++ b/components/desktop/src/pages/Marketplace/Cart/ui/CartPage.vue @@ -11,7 +11,7 @@ import { import { BaseCard, BaseButton, BaseChip, EmptyState } from 'src/shared/ui/base'; import { KUHeaderBar } from 'src/widgets/Marketplace/KUHeaderBar'; import { marketplaceUnitShort } from 'src/shared/lib/consts'; -import { useMarketplaceRealtime, getMembershipFeePercent } from 'src/shared/lib/marketplace'; +import { useMarketplaceRealtime, getMembershipFeePercent, applyMembershipFee } from 'src/shared/lib/marketplace'; /** * Эпик 16 / Story 16.1 + 16.2: страница корзины заказчика и оформление. @@ -35,12 +35,12 @@ const coopname = computed(() => String(route.params.coopname ?? '')); const symbol = computed(() => system.governSymbol); -function unitShort(u: string): string { - return marketplaceUnitShort(u as Parameters[0]); +function unitShort(u: string | null | undefined): string { + return marketplaceUnitShort((u ?? '') as Parameters[0]); } -function money(value: string | number): string { - return Number(value).toLocaleString('ru-RU'); +function money(value: string | number | null | undefined): string { + return Number(value ?? 0).toLocaleString('ru-RU'); } // Низкоуровневый коммит количества (целое ≥ 1). Кламп делает changeQty. @@ -156,8 +156,8 @@ useMarketplaceRealtime( // requirement b6: членский взнос входит в общую стоимость заказа — сводка // показывает его отдельной строкой и итог с учётом взноса. const feePercent = ref(0); -const feeAmount = computed(() => (cartStore.totalCost * feePercent.value) / 100); -const totalWithFee = computed(() => cartStore.totalCost + feeAmount.value); +const totalWithFee = computed(() => applyMembershipFee(Number(cartStore.totalCost), feePercent.value)); +const feeAmount = computed(() => totalWithFee.value - Number(cartStore.totalCost)); onMounted(async () => { try { diff --git a/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/AddToCartDialog.vue b/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/AddToCartDialog.vue index 00c1f2af8b5..8aae6f1c855 100644 --- a/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/AddToCartDialog.vue +++ b/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/AddToCartDialog.vue @@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'; import { FailAlert, SuccessAlert } from 'src/shared/api'; import { useSystemStore } from 'src/entities/System/model'; import { useMarketplaceCartStore } from 'src/entities/MarketplaceCart'; +import { applyMembershipFee } from 'src/shared/lib/marketplace'; import { BaseDialog, BaseInput, BaseButton } from 'src/shared/ui/base'; import type { MarketplaceOfferView } from '../types'; @@ -29,10 +30,16 @@ const cartStore = useMarketplaceCartStore(); const router = useRouter(); const route = useRoute(); -const props = defineProps<{ - modelValue: boolean; - offer: CartOffer | null; -}>(); +const props = withDefaults( + defineProps<{ + modelValue: boolean; + offer: CartOffer | null; + // Единая ставка членского взноса кооператива, проценты (requirement b6). + // Цена и итог показываются заказчику с учётом взноса — как в каталоге. + feePercent?: number; + }>(), + { feePercent: 0 }, +); const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void; @@ -64,10 +71,14 @@ const maxQuantity = computed(() => { return props.offer.quantity_available; }); -const totalSum = computed(() => { +const priceWithFee = computed(() => { if (!props.offer) return 0; + return applyMembershipFee(Number(props.offer.price_per_unit), props.feePercent); +}); + +const totalSum = computed(() => { const q = Number(quantity.value) || 0; - return q * Number(props.offer.price_per_unit); + return q * priceWithFee.value; }); // Сколько этой позиции уже в корзине — подсказка, чтобы заказчик не дублировал. @@ -148,7 +159,9 @@ BaseDialog( .add-to-cart__note(v-if="alreadyInCart > 0") | Уже в корзине: {{ alreadyInCart }} {{ unitLabel }} — добавление суммируется. .add-to-cart__price(v-if="offer") - | Цена: {{ Number(offer.price_per_unit).toLocaleString('ru-RU') }} {{ system.governSymbol }} за {{ unitLabel }} + | Цена: {{ priceWithFee.toLocaleString('ru-RU') }} {{ system.governSymbol }} за {{ unitLabel }} + template(v-if="feePercent > 0") + | (с членским взносом {{ feePercent }}%) .add-to-cart__total(v-if="offer") | Итого: {{ totalSum.toLocaleString('ru-RU') }} {{ system.governSymbol }} template(#footer) diff --git a/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/MarketplaceCatalogPage.vue b/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/MarketplaceCatalogPage.vue index 540b028a161..e780517d7f4 100644 --- a/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/MarketplaceCatalogPage.vue +++ b/components/desktop/src/pages/Marketplace/MarketplaceCatalog/ui/MarketplaceCatalogPage.vue @@ -410,7 +410,8 @@ q-page.catalog(role="region", aria-label="Каталог Стола заказо AddToCartDialog( v-model="cartDialogOpen", - :offer="cartDialogOffer" + :offer="cartDialogOffer", + :fee-percent="feePercent" ) diff --git a/components/desktop/src/pages/Marketplace/MarketplaceOfferDetail/api/index.ts b/components/desktop/src/pages/Marketplace/MarketplaceOfferDetail/api/index.ts index f9a15fa4688..13c15441274 100644 --- a/components/desktop/src/pages/Marketplace/MarketplaceOfferDetail/api/index.ts +++ b/components/desktop/src/pages/Marketplace/MarketplaceOfferDetail/api/index.ts @@ -12,5 +12,5 @@ export async function fetchOffer(id: string): Promise { : `В наличии: ${offer.value.quantity_available} ${unitShort.value}`; }); +// requirement b6: единая ставка членского взноса входит в цену, которую видит +// заказчик — как в карточке каталога (CatalogOfferCard). Раздельная +// себестоимость без взноса остаётся только на столе поставщика. +const feePercent = ref(0); +const priceWithFee = computed(() => + offer.value ? applyMembershipFee(Number(offer.value.price_per_unit), feePercent.value) : 0, +); const priceLabel = computed(() => offer.value - ? `${Number(offer.value.price_per_unit).toLocaleString('ru-RU')} ${system.governSymbol} / ${unitShort.value}` + ? `${priceWithFee.value.toLocaleString('ru-RU')} ${system.governSymbol} / ${unitShort.value}` : '', ); @@ -157,6 +164,11 @@ useMarketplaceRealtime( ); onMounted(async () => { + try { + feePercent.value = await getMembershipFeePercent(); + } catch { + // Без ставки показываем цену без строки взноса. + } await load(); // В режиме заказа нужен текущий КУ (для кнопки «В корзину» и диалога). if (!readonly.value) { @@ -209,6 +221,7 @@ q-page.offer-detail(role="region", aria-label="Описание предложе span {{ offer.supplier_name }} .offer-detail__price {{ priceLabel }} + .offer-detail__fee-note(v-if="feePercent > 0") Цена с членским взносом {{ feePercent }}% BaseButton( v-if="!readonly", @@ -257,7 +270,8 @@ q-page.offer-detail(role="region", aria-label="Описание предложе AddToCartDialog( v-if="!readonly", v-model="cartDialogOpen", - :offer="offer" + :offer="offer", + :fee-percent="feePercent" ) @@ -333,6 +347,12 @@ q-page.offer-detail(role="region", aria-label="Описание предложе color: var(--p-primary-strong); } + &__fee-note { + font-size: var(--p-fs-body-sm); + color: var(--p-ink-3); + margin-top: calc(var(--p-1, 4px) * -1); + } + &__sections { display: flex; flex-direction: column; diff --git a/components/desktop/src/pages/Marketplace/MyOrders/ui/MyOrdersPage.vue b/components/desktop/src/pages/Marketplace/MyOrders/ui/MyOrdersPage.vue index b01b91e6aba..86034570f13 100644 --- a/components/desktop/src/pages/Marketplace/MyOrders/ui/MyOrdersPage.vue +++ b/components/desktop/src/pages/Marketplace/MyOrders/ui/MyOrdersPage.vue @@ -143,13 +143,13 @@ function groupByCheckout(list: MarketplaceOrderView[]): OrderGroup[] { } const result: OrderGroup[] = Array.from(map.entries()).map(([key, orders]) => { const first = orders[0]; - const recent = Math.max(...orders.map((o) => new Date(o.updated_at).getTime())); + const recent = Math.max(...orders.map((o) => new Date(String(o.updated_at)).getTime())); return { key, isAggregate: orders.length > 1, orders, count: orders.length, - totalCost: orders.reduce((s, o) => s + Number(o.total_cost), 0), + totalCost: orders.reduce((s, o) => s + Number(o.total_cost_with_fee), 0), deliveryName: first?.delivery_point_name || first?.delivery_braname || '', createdAt: first?.created_at ?? '', recent, @@ -250,7 +250,7 @@ async function onLoadMore(): Promise { function confirmCancel(order: MarketplaceOrderView): void { Dialog.create({ title: 'Отменить заказ?', - message: `Заказ № ${order.id.slice(0, 8)} (${order.quantity} ед., ${order.total_cost} ₽) будет отменён. Средства разблокируются на кошельке Стола заказов.`, + message: `Заказ № ${order.id.slice(0, 8)} (${order.quantity} ед., ${money(order.total_cost_with_fee)} ₽) будет отменён. Средства разблокируются на кошельке Стола заказов.`, cancel: { label: 'Не отменять', flat: true }, ok: { label: 'Отменить заказ', color: 'negative', unelevated: true }, persistent: true, diff --git a/components/desktop/src/pages/Marketplace/OffererIncomingOrders/ui/OffererIncomingOrdersPage.vue b/components/desktop/src/pages/Marketplace/OffererIncomingOrders/ui/OffererIncomingOrdersPage.vue index 7762cc1c980..ce1d69090f5 100644 --- a/components/desktop/src/pages/Marketplace/OffererIncomingOrders/ui/OffererIncomingOrdersPage.vue +++ b/components/desktop/src/pages/Marketplace/OffererIncomingOrders/ui/OffererIncomingOrdersPage.vue @@ -122,6 +122,8 @@ interface SupplierParty { orders: MarketplaceOrderView[]; totalUnits: number; totalCost: number; + /** Сумма членских взносов пайщиков по заказам партии (requirement b6) — справочно, не деньги поставщика. */ + totalFee: number; /** Целевой минимальный объём поставки на этот КУ (для накопителя). */ minVolume: number | null; stageStatus: MarketplaceOrderStatusView; @@ -150,6 +152,7 @@ const parties = computed(() => { orders: [], totalUnits: 0, totalCost: 0, + totalFee: 0, minVolume: collecting ? (minVolumeMap.value.get(`${o.offer_id}::${o.delivery_braname}`) ?? null) : null, @@ -160,6 +163,7 @@ const parties = computed(() => { p.orders.push(o); p.totalUnits += o.quantity; p.totalCost += parseFloat(o.total_cost) || 0; + p.totalFee += Number(o.membership_fee ?? 0) || 0; const candidate = STAGE_RANK[o.status]; const current = STAGE_RANK[p.stageStatus]; if (candidate < 90 && (current >= 90 || candidate < current)) { @@ -340,7 +344,8 @@ q-page.incoming-orders(role='region', aria-label='Входящие заказы :bar-color='barColor(p)', :members='[]', total-label='Итого', - :total-value='`${formatCost(p.totalCost)} · ${p.totalUnits} ${p.unitLabel}`' + :total-value='`${formatCost(p.totalCost)} · ${p.totalUnits} ${p.unitLabel}`', + :total-fee-note='p.totalFee > 0 ? `С учётом взноса пайщиков: ${formatCost(p.totalCost + p.totalFee)}` : undefined' ) template(#actions) template(v-if='p.kind === "collecting"') diff --git a/components/desktop/src/pages/Marketplace/OffererSupplyPreparation/lib/ttn.ts b/components/desktop/src/pages/Marketplace/OffererSupplyPreparation/lib/ttn.ts index 60b0c1baf41..4c770607fdc 100644 --- a/components/desktop/src/pages/Marketplace/OffererSupplyPreparation/lib/ttn.ts +++ b/components/desktop/src/pages/Marketplace/OffererSupplyPreparation/lib/ttn.ts @@ -64,7 +64,7 @@ export function buildTtnData( const boxes = unitsPerBox && unitsPerBox > 0 ? Math.ceil(o.quantity / unitsPerBox) : undefined; return { sku: o.offer_id ? String(o.offer_id).slice(0, 8) : '—', - title: o.product_name, + title: o.product_name || 'Товар по предложению', qty: o.quantity, unit: marketplaceUnitShort(o.unit_of_measure), price: parseFloat(o.price_per_unit) || 0, diff --git a/components/desktop/src/pages/Marketplace/OnboardingMemberPickCpp/ui/OnboardingMemberPickCppPage.vue b/components/desktop/src/pages/Marketplace/OnboardingMemberPickCpp/ui/OnboardingMemberPickCppPage.vue index 82ce315095a..c91aa8820d3 100644 --- a/components/desktop/src/pages/Marketplace/OnboardingMemberPickCpp/ui/OnboardingMemberPickCppPage.vue +++ b/components/desktop/src/pages/Marketplace/OnboardingMemberPickCpp/ui/OnboardingMemberPickCppPage.vue @@ -230,8 +230,8 @@ q-page.mp-role-orderer.mp-member-cpp(role="region", aria-label="Подключе .mp-member-cpp__head-icon q-icon(name="location_on", size="22px") .mp-member-cpp__head-text - .text-subtitle1.text-weight-medium Выберите пункт выдачи - .text-body2.text-grey-7 Где будете забирать заказы. Каталог отфильтруется под выбранный участок. + .text-subtitle1.text-weight-medium Выберите пункт выдачи заказов + .text-body2.text-grey-7 Выбирайте участок, где вам удобно забирать заказы, — сменить его можно в любой момент. BaseChip.mp-member-cpp__picked(v-if="selectedName", variant="pos", size="sm") q-icon(name="check", size="14px") | {{ selectedName }} diff --git a/components/desktop/src/pages/Marketplace/OperatorBranchEconomy/ui/OperatorBranchEconomyPage.vue b/components/desktop/src/pages/Marketplace/OperatorBranchEconomy/ui/OperatorBranchEconomyPage.vue index c3a575ed5fb..ee0c3c6dc74 100644 --- a/components/desktop/src/pages/Marketplace/OperatorBranchEconomy/ui/OperatorBranchEconomyPage.vue +++ b/components/desktop/src/pages/Marketplace/OperatorBranchEconomy/ui/OperatorBranchEconomyPage.vue @@ -170,7 +170,7 @@ const planSaving = ref(false) function planDueLabel(plan: ExpensePlanView): string { if (plan.priority === Zeus.ExpensePlanPriority.URGENT) return 'Срочный' if (plan.priority === Zeus.ExpensePlanPriority.OPTIONAL) return 'Необязательный' - return plan.due_date ? new Date(plan.due_date).toLocaleDateString('ru-RU') : '—' + return plan.due_date ? new Date(String(plan.due_date)).toLocaleDateString('ru-RU') : '—' } async function onAddPlan(): Promise { diff --git a/components/desktop/src/pages/Marketplace/OperatorReception/ui/OperatorReceptionPage.vue b/components/desktop/src/pages/Marketplace/OperatorReception/ui/OperatorReceptionPage.vue index f1fe2a61c6f..cc8d008d31f 100644 --- a/components/desktop/src/pages/Marketplace/OperatorReception/ui/OperatorReceptionPage.vue +++ b/components/desktop/src/pages/Marketplace/OperatorReception/ui/OperatorReceptionPage.vue @@ -258,7 +258,7 @@ function aggregateLines(orders: MarketplaceSupplierPickupOrderView[]): DeliveryL map.set(key, { key, productName: o.product_name || 'Товар по предложению', - unit: o.unit_of_measure, + unit: o.unit_of_measure ?? '', quantity: qty, boxes, }); @@ -382,7 +382,7 @@ function groupOrdersByProduct(orders: MarketplaceSupplierPickupOrderView[]): Pic map.set(key, { key, productName: o.product_name || 'Товар по предложению', - unit: o.unit_of_measure, + unit: o.unit_of_measure ?? '', orderedTotal: qty, orders: [o], }); diff --git a/components/desktop/src/pages/Marketplace/OrdererConsolidated/ui/OrdererConsolidatedPage.vue b/components/desktop/src/pages/Marketplace/OrdererConsolidated/ui/OrdererConsolidatedPage.vue index 82de6100109..5a65cd2603c 100644 --- a/components/desktop/src/pages/Marketplace/OrdererConsolidated/ui/OrdererConsolidatedPage.vue +++ b/components/desktop/src/pages/Marketplace/OrdererConsolidated/ui/OrdererConsolidatedPage.vue @@ -88,6 +88,7 @@ interface CollectiveParty { orders: MarketplaceOrderView[]; /** Свой вклад (сумма собственных заказов в этой партии). */ ownUnits: number; + /** С учётом членского взноса (requirement b6) — то, что заказчик реально платит. */ ownCost: number; /** Накоплено всеми пайщиками (с бэкенда): пул сбора или объём партии. */ groupAccumulated: number | null; @@ -127,7 +128,7 @@ const parties = computed(() => { } p.orders.push(o); p.ownUnits += o.quantity; - p.ownCost += parseFloat(o.total_cost) || 0; + p.ownCost += Number(o.total_cost_with_fee); const candidate = STAGE_RANK[o.status]; const current = STAGE_RANK[p.stageStatus]; if (candidate < 90 && (current >= 90 || candidate < current)) { diff --git a/components/desktop/src/pages/Marketplace/OrdererOrderDetail/ui/OrdererOrderDetailPage.vue b/components/desktop/src/pages/Marketplace/OrdererOrderDetail/ui/OrdererOrderDetailPage.vue index 743f72d0753..8f2b0450ea5 100644 --- a/components/desktop/src/pages/Marketplace/OrdererOrderDetail/ui/OrdererOrderDetailPage.vue +++ b/components/desktop/src/pages/Marketplace/OrdererOrderDetail/ui/OrdererOrderDetailPage.vue @@ -46,6 +46,14 @@ const status = computed(() => (order.value ? orderStatusDisplay(order.value.stat const unitShort = computed(() => marketplaceUnitShort(order.value?.unit_of_measure)); const cancellable = computed(() => order.value?.status === 'ACTIVE'); +// requirement b6: заказчику показываем цену С членским взносом (как в +// каталоге) — order.total_cost_with_fee уже посчитан бэком (total_cost + +// membership_fee, зафиксированный контрактом по ставке на момент заказа). +const totalWithFee = computed(() => (order.value ? Number(order.value.total_cost_with_fee) : 0)); +const unitPriceWithFee = computed(() => + order.value && order.value.quantity > 0 ? totalWithFee.value / order.value.quantity : 0, +); + const pvzName = computed(() => order.value?.delivery_point_name || order.value?.delivery_braname || ''); const pvzAddress = computed(() => order.value?.delivery_point_address || ''); @@ -129,7 +137,7 @@ function confirmCancel(): void { if (!o) return; Dialog.create({ title: 'Отменить заказ?', - message: `Заказ № ${o.id.slice(0, 8)} (${o.quantity} ед., ${o.total_cost} ₽) будет отменён. Средства разблокируются на кошельке Стола заказов.`, + message: `Заказ № ${o.id.slice(0, 8)} (${o.quantity} ед., ${formatPrice(o.total_cost_with_fee)}) будет отменён. Средства разблокируются на кошельке Стола заказов.`, cancel: { label: 'Не отменять', flat: true }, ok: { label: 'Отменить заказ', color: 'negative', unelevated: true }, persistent: true, @@ -199,13 +207,13 @@ q-page.order-detail(role="region", aria-label="Заказ") .order-detail__facts .order-detail__fact .order-detail__fact-label Сумма заказа - .order-detail__fact-value--money {{ formatPrice(order.total_cost) }} + .order-detail__fact-value--money {{ formatPrice(String(totalWithFee)) }} .order-detail__fact .order-detail__fact-label Количество .order-detail__fact-value {{ order.quantity }} {{ unitShort }} .order-detail__fact .order-detail__fact-label Цена за единицу - .order-detail__fact-value {{ formatPrice(order.price_per_unit) }} + .order-detail__fact-value {{ formatPrice(String(unitPriceWithFee)) }} BaseCard.order-detail__card(v-if="pvzName || pvzAddress") template(#head) diff --git a/components/desktop/src/pages/Marketplace/SupplierRegistry/ui/SupplierRegistryPage.vue b/components/desktop/src/pages/Marketplace/SupplierRegistry/ui/SupplierRegistryPage.vue index bc6aad50a93..c0ecaf52eaf 100644 --- a/components/desktop/src/pages/Marketplace/SupplierRegistry/ui/SupplierRegistryPage.vue +++ b/components/desktop/src/pages/Marketplace/SupplierRegistry/ui/SupplierRegistryPage.vue @@ -1,5 +1,6 @@