feat(web): supply line retail override column
Some checks are pending
Some checks are pending
В таблице строк приёмки добавлена колонка «Розничная (карточка)». - Значение по умолчанию — текущая дефолтная розничная цена товара (берётся из 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:
parent
095ac04d31
commit
23561fca2e
|
|
@ -94,6 +94,8 @@ export interface SupplyLineDto {
|
||||||
id: string | null; productId: string;
|
id: string | null; productId: string;
|
||||||
productName: string | null; productArticle: string | null; unitName: string | null;
|
productName: string | null; productArticle: string | null; unitName: string | null;
|
||||||
quantity: number; unitPrice: number; lineTotal: number; sortOrder: number;
|
quantity: number; unitPrice: number; lineTotal: number; sortOrder: number;
|
||||||
|
retailPriceManuallyOverridden: boolean; retailPriceOverride: number | null;
|
||||||
|
currentRetailPrice: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SupplyDto {
|
export interface SupplyDto {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ interface LineRow {
|
||||||
unitName: string | null
|
unitName: string | null
|
||||||
quantity: number
|
quantity: number
|
||||||
unitPrice: number
|
unitPrice: number
|
||||||
|
// Розничная цена с карточки товара на момент загрузки документа
|
||||||
|
// (read-only baseline). Используется как placeholder для ручного override.
|
||||||
|
currentRetailPrice: number | null
|
||||||
|
retailPriceManuallyOverridden: boolean
|
||||||
|
retailPriceOverride: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Form {
|
interface Form {
|
||||||
|
|
@ -81,6 +86,9 @@ export function SupplyEditPage() {
|
||||||
unitName: l.unitName,
|
unitName: l.unitName,
|
||||||
quantity: l.quantity,
|
quantity: l.quantity,
|
||||||
unitPrice: l.unitPrice,
|
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,
|
supplierInvoiceNumber: form.supplierInvoiceNumber || null,
|
||||||
supplierInvoiceDate: form.supplierInvoiceDate ? new Date(form.supplierInvoiceDate).toISOString() : null,
|
supplierInvoiceDate: form.supplierInvoiceDate ? new Date(form.supplierInvoiceDate).toISOString() : null,
|
||||||
notes: form.notes || 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) {
|
if (isNew) {
|
||||||
return (await api.post<SupplyDto>('/api/purchases/supplies', payload)).data
|
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 onSubmit = (e: FormEvent) => { e.preventDefault(); save.mutate() }
|
||||||
|
|
||||||
const addLineFromProduct = (p: Product) => {
|
const addLineFromProduct = (p: Product) => {
|
||||||
|
const defaultRetail = p.prices?.[0]?.amount ?? null
|
||||||
setForm({
|
setForm({
|
||||||
...form,
|
...form,
|
||||||
lines: [...form.lines, {
|
lines: [...form.lines, {
|
||||||
|
|
@ -175,7 +190,10 @@ export function SupplyEditPage() {
|
||||||
productArticle: p.article,
|
productArticle: p.article,
|
||||||
unitName: p.unitName,
|
unitName: p.unitName,
|
||||||
quantity: 1,
|
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-[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-[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 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>
|
<th className="py-2 pl-3 w-[40px]"></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -322,6 +341,18 @@ export function SupplyEditPage() {
|
||||||
onChange={(n) => updateLine(i, { unitPrice: n ?? 0 })}
|
onChange={(n) => updateLine(i, { unitPrice: n ?? 0 })}
|
||||||
currencyCode={currencies.data?.find((c) => c.id === form.currencyId)?.code}
|
currencyCode={currencies.data?.find((c) => c.id === form.currencyId)?.code}
|
||||||
currencySymbol={currencies.data?.find((c) => c.id === form.currencyId)?.symbol}
|
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>
|
||||||
<td className="py-2 px-3 text-right font-mono font-semibold">
|
<td className="py-2 px-3 text-right font-mono font-semibold">
|
||||||
|
|
@ -339,7 +370,7 @@ export function SupplyEditPage() {
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<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>
|
||||||
<td className="py-3 px-3 text-right font-mono text-lg font-bold">
|
<td className="py-3 px-3 text-right font-mono text-lg font-bold">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue