feat(web): supply line retail override column
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 42s
CI / Web (React + Vite) (push) Successful in 34s
Docker Web / Build + push Web (push) Successful in 26s
Docker Web / Deploy Web on stage (push) Successful in 11s

В таблице строк приёмки добавлена колонка «Розничная (карточка)».
- Значение по умолчанию — текущая дефолтная розничная цена товара
  (берётся из ProductDto.prices при подборе или из SupplyLineDto.
  currentRetailPrice при загрузке существующего документа).
- Любая ручная правка ставит retailPriceManuallyOverridden=true и
  записывает retailPriceOverride. При проведении документа этот
  override применяется к Product.Prices[default] вместо автонаценки.
- В payload PUT/POST шлём retailPriceManuallyOverridden и
  retailPriceOverride (либо null если override снят).
- types.SupplyLineDto расширен полями currentRetailPrice /
  retailPriceManuallyOverridden / retailPriceOverride.
- В addLineFromProduct unitPrice fallbacks теперь учитывают cost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-25 21:12:12 +05:00
parent 095ac04d31
commit 23561fca2e
2 changed files with 36 additions and 3 deletions

View file

@ -94,6 +94,8 @@ export interface SupplyLineDto {
id: string | null; productId: string;
productName: string | null; productArticle: string | null; unitName: string | null;
quantity: number; unitPrice: number; lineTotal: number; sortOrder: number;
retailPriceManuallyOverridden: boolean; retailPriceOverride: number | null;
currentRetailPrice: number | null;
}
export interface SupplyDto {

View file

@ -18,6 +18,11 @@ interface LineRow {
unitName: string | null
quantity: number
unitPrice: number
// Розничная цена с карточки товара на момент загрузки документа
// (read-only baseline). Используется как placeholder для ручного override.
currentRetailPrice: number | null
retailPriceManuallyOverridden: boolean
retailPriceOverride: number | null
}
interface Form {
@ -81,6 +86,9 @@ export function SupplyEditPage() {
unitName: l.unitName,
quantity: l.quantity,
unitPrice: l.unitPrice,
currentRetailPrice: l.currentRetailPrice ?? null,
retailPriceManuallyOverridden: l.retailPriceManuallyOverridden ?? false,
retailPriceOverride: l.retailPriceOverride ?? null,
})),
})
}
@ -121,7 +129,13 @@ export function SupplyEditPage() {
supplierInvoiceNumber: form.supplierInvoiceNumber || null,
supplierInvoiceDate: form.supplierInvoiceDate ? new Date(form.supplierInvoiceDate).toISOString() : null,
notes: form.notes || null,
lines: form.lines.map((l) => ({ productId: l.productId, quantity: l.quantity, unitPrice: l.unitPrice })),
lines: form.lines.map((l) => ({
productId: l.productId,
quantity: l.quantity,
unitPrice: l.unitPrice,
retailPriceManuallyOverridden: l.retailPriceManuallyOverridden,
retailPriceOverride: l.retailPriceManuallyOverridden ? l.retailPriceOverride : null,
})),
}
if (isNew) {
return (await api.post<SupplyDto>('/api/purchases/supplies', payload)).data
@ -167,6 +181,7 @@ export function SupplyEditPage() {
const onSubmit = (e: FormEvent) => { e.preventDefault(); save.mutate() }
const addLineFromProduct = (p: Product) => {
const defaultRetail = p.prices?.[0]?.amount ?? null
setForm({
...form,
lines: [...form.lines, {
@ -175,7 +190,10 @@ export function SupplyEditPage() {
productArticle: p.article,
unitName: p.unitName,
quantity: 1,
unitPrice: p.referencePrice ?? 0,
unitPrice: p.referencePrice ?? p.cost ?? 0,
currentRetailPrice: defaultRetail,
retailPriceManuallyOverridden: false,
retailPriceOverride: null,
}],
})
}
@ -299,6 +317,7 @@ export function SupplyEditPage() {
<th className="py-2 px-3 font-medium text-xs uppercase tracking-wide text-slate-500 w-[90px]">Ед.</th>
<th className="py-2 px-3 font-medium text-xs uppercase tracking-wide text-slate-500 w-[140px] text-right">Количество</th>
<th className="py-2 px-3 font-medium text-xs uppercase tracking-wide text-slate-500 w-[140px] text-right">Цена</th>
<th className="py-2 px-3 font-medium text-xs uppercase tracking-wide text-slate-500 w-[160px] text-right">Розничная (карточка)</th>
<th className="py-2 px-3 font-medium text-xs uppercase tracking-wide text-slate-500 w-[160px] text-right">Сумма</th>
<th className="py-2 pl-3 w-[40px]"></th>
</tr>
@ -322,6 +341,18 @@ export function SupplyEditPage() {
onChange={(n) => updateLine(i, { unitPrice: n ?? 0 })}
currencyCode={currencies.data?.find((c) => c.id === form.currencyId)?.code}
currencySymbol={currencies.data?.find((c) => c.id === form.currencyId)?.symbol}
/>
</td>
<td className="py-2 px-3">
<MoneyInput disabled={isPosted}
value={l.retailPriceManuallyOverridden ? l.retailPriceOverride : l.currentRetailPrice}
onChange={(n) => updateLine(i, {
retailPriceManuallyOverridden: true,
retailPriceOverride: n,
})}
currencyCode={currencies.data?.find((c) => c.id === form.currencyId)?.code}
currencySymbol={currencies.data?.find((c) => c.id === form.currencyId)?.symbol}
placeholder={l.currentRetailPrice != null ? String(l.currentRetailPrice) : '—'}
/>
</td>
<td className="py-2 px-3 text-right font-mono font-semibold">
@ -339,7 +370,7 @@ export function SupplyEditPage() {
</tbody>
<tfoot>
<tr>
<td colSpan={4} className="py-3 pr-3 text-right text-sm font-semibold text-slate-600 dark:text-slate-300">
<td colSpan={5} className="py-3 pr-3 text-right text-sm font-semibold text-slate-600 dark:text-slate-300">
Итого:
</td>
<td className="py-3 px-3 text-right font-mono text-lg font-bold">