feat(org-settings): валюта read-only, тянется из страны (как НДС)
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 30s
CI / Web (React + Vite) (push) Successful in 22s
Docker Images / API image (push) Successful in 40s
Docker Images / Web image (push) Successful in 24s
Docker Images / Deploy stage (push) Successful in 11s

- Currency в настройках больше не выбирается, показывается disabled
  как "KZT (₸)", источник правды — Country.DefaultCurrencyId.
- Backend: OrgSettingsInput больше не принимает DefaultCurrencyId;
  Update синхронизирует Organization.DefaultCurrencyId со страной.
- UX: страна — единственный редактируемый вход, определяет и НДС, и валюту.
- Мульти-валютный режим (Organization.MultiCurrencyEnabled) остаётся
  галкой; выбор валюты в закупках/продажах/карточке товара по-прежнему
  скрыт когда флаг выключен.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-24 12:08:30 +05:00
parent 9d8c386def
commit 51bef16758
2 changed files with 15 additions and 23 deletions

View file

@ -32,10 +32,10 @@ public record OrgSettingsDto(
decimal VatRate, decimal VatRate,
bool ShowVatEnabledOnProduct); bool ShowVatEnabledOnProduct);
// DefaultCurrencyId не принимается — он read-only, выводится из страны (Country.DefaultCurrencyId).
public record OrgSettingsInput( public record OrgSettingsInput(
string Name, string Name,
string CountryCode, string CountryCode,
Guid? DefaultCurrencyId,
bool MultiCurrencyEnabled, bool MultiCurrencyEnabled,
bool ShowVatEnabledOnProduct); bool ShowVatEnabledOnProduct);
@ -62,7 +62,11 @@ public async Task<ActionResult<OrgSettingsDto>> Update([FromBody] OrgSettingsInp
o.Name = input.Name; o.Name = input.Name;
o.CountryCode = input.CountryCode; o.CountryCode = input.CountryCode;
o.DefaultCurrencyId = input.DefaultCurrencyId; // Валюта организации жёстко следует за страной — не принимается от клиента.
o.DefaultCurrencyId = await _db.Countries
.Where(c => c.Code == input.CountryCode)
.Select(c => c.DefaultCurrencyId)
.FirstOrDefaultAsync(ct);
o.MultiCurrencyEnabled = input.MultiCurrencyEnabled; o.MultiCurrencyEnabled = input.MultiCurrencyEnabled;
o.ShowVatEnabledOnProduct = input.ShowVatEnabledOnProduct; o.ShowVatEnabledOnProduct = input.ShowVatEnabledOnProduct;
await _db.SaveChangesAsync(ct); await _db.SaveChangesAsync(ct);

View file

@ -5,31 +5,27 @@ import { api } from '@/lib/api'
import { PageHeader } from '@/components/PageHeader' import { PageHeader } from '@/components/PageHeader'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { Field, TextInput, Select, Checkbox } from '@/components/Field' import { Field, TextInput, Select, Checkbox } from '@/components/Field'
import { useCurrencies, useCountries } from '@/lib/useLookups' import { useCountries } from '@/lib/useLookups'
import { useOrgSettings, type OrgSettings } from '@/lib/useOrgSettings' import { useOrgSettings, type OrgSettings } from '@/lib/useOrgSettings'
export function OrganizationSettingsPage() { export function OrganizationSettingsPage() {
const qc = useQueryClient() const qc = useQueryClient()
const settings = useOrgSettings() const settings = useOrgSettings()
const currencies = useCurrencies()
const countries = useCountries() const countries = useCountries()
const [form, setForm] = useState<OrgSettings | null>(null) const [form, setForm] = useState<OrgSettings | null>(null)
useEffect(() => { if (settings.data && !form) setForm(settings.data) }, [settings.data, form]) useEffect(() => { if (settings.data && !form) setForm(settings.data) }, [settings.data, form])
// При смене страны подтягиваем её валюту и ставку НДС (оба из справочника стран). // При смене страны подтягиваем её валюту и ставку НДС (оба read-only, из справочника стран).
const onCountryChange = (countryCode: string) => { const onCountryChange = (countryCode: string) => {
if (!form) return if (!form) return
const country = countries.data?.find((c) => c.code === countryCode) const country = countries.data?.find((c) => c.code === countryCode)
const currency = country?.defaultCurrencyId
? currencies.data?.find((c) => c.id === country.defaultCurrencyId)
: undefined
setForm({ setForm({
...form, ...form,
countryCode, countryCode,
defaultCurrencyId: currency?.id ?? country?.defaultCurrencyId ?? null, defaultCurrencyId: country?.defaultCurrencyId ?? null,
defaultCurrencyCode: currency?.code ?? country?.defaultCurrencyCode ?? null, defaultCurrencyCode: country?.defaultCurrencyCode ?? null,
defaultCurrencySymbol: currency?.symbol ?? country?.defaultCurrencySymbol ?? null, defaultCurrencySymbol: country?.defaultCurrencySymbol ?? null,
vatRate: country?.vatRate ?? 0, vatRate: country?.vatRate ?? 0,
}) })
} }
@ -40,7 +36,6 @@ export function OrganizationSettingsPage() {
const payload = { const payload = {
name: form.name, name: form.name,
countryCode: form.countryCode, countryCode: form.countryCode,
defaultCurrencyId: form.defaultCurrencyId,
multiCurrencyEnabled: form.multiCurrencyEnabled, multiCurrencyEnabled: form.multiCurrencyEnabled,
showVatEnabledOnProduct: form.showVatEnabledOnProduct, showVatEnabledOnProduct: form.showVatEnabledOnProduct,
} }
@ -71,17 +66,10 @@ export function OrganizationSettingsPage() {
</Select> </Select>
</Field> </Field>
<Field label="Валюта по умолчанию"> <Field label="Валюта по умолчанию">
<Select <TextInput
value={form.defaultCurrencyId ?? ''} value={form.defaultCurrencyCode ? `${form.defaultCurrencyCode} (${form.defaultCurrencySymbol ?? ''})` : '—'}
onChange={(e) => { disabled
const id = e.target.value />
const c = currencies.data?.find((x) => x.id === id)
setForm({ ...form, defaultCurrencyId: id || null, defaultCurrencyCode: c?.code ?? null, defaultCurrencySymbol: c?.symbol ?? null })
}}
>
<option value=""></option>
{currencies.data?.map((c) => <option key={c.id} value={c.id}>{c.code} ({c.symbol})</option>)}
</Select>
</Field> </Field>
</div> </div>