SKILL: Uporządkowanie skills domenowych - podział na mniejsze pliki i wspólna numeracja
This commit is contained in:
@@ -0,0 +1,554 @@
|
||||
# HANDEL06 — Magazyn, zasoby, partie, obroty
|
||||
|
||||
> Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../handel.md](../handel.md).
|
||||
|
||||
> Sekcja opisuje **odczyt** efektów magazynowych dokumentu (zasoby, obroty) oraz
|
||||
> **sterowanie** rozchodem przez wskazanie partii (`GrupaDostaw`) i kontekst wyceny
|
||||
> (FIFO/LIFO/wg dostaw). Cały kod operuje wyłącznie na **publicznym kontrakcie**
|
||||
> platformy i jest zgodny z C# 10.
|
||||
>
|
||||
> **Klucz do zrozumienia całej sekcji:** magazyn księguje obroty i zasoby **dopiero po
|
||||
> `Session.Save()`** dokumentu. Samo `Commit()`/`CommitUI()` w transakcji nie nalicza
|
||||
> stanów. W bazie Demo działa `StanUjemnyVerifier` — **rozchód** (FV/WZ/RW) wymaga
|
||||
> wcześniejszego **zapisanego** przyjęcia (PW/PZ) tego towaru; w przeciwnym razie zapis
|
||||
> rozchodu zostanie odrzucony.
|
||||
>
|
||||
> **Słowniczek typów (moduł `Soneta.Magazyny`):**
|
||||
> - `Zasob` (tabela `Zasoby`) — stan towaru: ilość na partii w danym magazynie i okresie.
|
||||
> - `Obrot` (tabela `Obroty`) — pojedynczy ruch (przychód lub rozchód) wiążący partie.
|
||||
> - `GrupaDostaw` (tabela `GrupyDostaw`, namespace `Soneta.Magazyny.Dostawy`) — **partia**
|
||||
> towaru (identyfikowana `Numer` + `Towar`).
|
||||
> - `OkresMagazynowy` (tabela `OkresyMag`) — przedział czasu, w którym ewidencjonowane są
|
||||
> obroty/zasoby; po zamknięciu blokuje modyfikacje.
|
||||
> - `PartiaTowaru` — **subrow** (nie tabela) opisujący stronę partii w `Obrot`/`Zasob`:
|
||||
> `Dokument`, `PozycjaIdent`, `PartiaTowaru: GrupaDostaw`, `KontrahentPartii`, `Data`, `Czas`, `Typ`, `Wartosc`.
|
||||
> - Enum `KierunekPartii`: `Rozchód=-1`, `Brak=0`, `Przychód=1`.
|
||||
> - Enum `Magazyn.Algorytm` (`AlgorytmMagazynowy`): `FIFO=0`, `LIFO=1`, `NieLiczyćStanów=2`,
|
||||
> `WgDostawy=3`, `WgDostawyPrzyZatwierdzaniu=10`, `OdNajdroższych=4`, `OdNajtańszych=5`,
|
||||
> `WgCechyPozycji=6/7`, `WgCechyDokumentu=8/9`.
|
||||
>
|
||||
> Dostęp do modułu: `var mag = session.GetMagazyny();` → `mag.Zasoby`, `mag.Obroty`,
|
||||
> `mag.GrupyDostaw`, `mag.OkresyMag`, `mag.Magazyny`.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W31 — Przeglądanie zasobów utworzonych przez dokument przychodowy (`dok.Zasoby`)
|
||||
|
||||
**Cel:** po zapisaniu dokumentu przychodowego (PW/PZ/FZ) odczytać zasoby magazynowe,
|
||||
które ten dokument wprowadził na stan — np. żeby zweryfikować ilości albo powiązać je z
|
||||
partią.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Źródło | Uwaga |
|
||||
|---|---|---|
|
||||
| Zasoby utworzone bezpośrednio przez dokument | `dok.Zasoby` (`SubTable<Zasob>`) | filtr po `Partia.Dokument == dok` |
|
||||
| Zasoby łącznie z dokumentami zależnymi | `dok.ZasobyWszystkie` (`ListWithView`) | obejmuje powiązane dok. magazynowe |
|
||||
| Iteracja po module | `mag.Zasoby.WgTowar[towar, okres, magazyn]` | gdy nie mamy uchwytu do dokumentu |
|
||||
|
||||
**Pola i typy:** `dok.Zasoby: SubTable` (elementy `Soneta.Magazyny.Zasob`). `Zasob`:
|
||||
`Ilosc: Quantity`, `IloscRezerwowana: Quantity`, `Kierunek: KierunekPartii`,
|
||||
`Magazyn: Magazyn`, `Towar: Towar`, `Okres: OkresMagazynowy`, `Partia: PartiaTowaru` (subrow),
|
||||
`PartiaPierwotna: PartiaTowaru`.
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
// dok — zapisany dokument przychodowy (PW/PZ/FZ), po session.Save()
|
||||
var mag = session.GetMagazyny();
|
||||
|
||||
foreach (Zasob z in dok.Zasoby)
|
||||
{
|
||||
// strona partii zasobu: skąd pochodzi (dokument, pozycja, numer partii)
|
||||
GrupaDostaw partia = z.Partia.PartiaTowaru; // rekord partii (może być null dla prostej ewidencji)
|
||||
Console.WriteLine(
|
||||
$"{z.Towar.Kod} mag={z.Magazyn.Symbol} kierunek={z.Kierunek} " +
|
||||
$"ilość={z.Ilosc} partia={partia?.Numer}");
|
||||
}
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- `dok.Zasoby` jest **puste, dopóki nie wykonasz `session.Save()`** — przed zapisem magazyn
|
||||
nie zaksięgował zasobów (sam `Commit`/`CommitUI` nie wystarcza).
|
||||
- Wzorzec testowy: zapis dokumentu → `SaveDispose()` → odczyt na świeżej sesji po `Guid`,
|
||||
bo po `Save()` w środku testu okno edycji się zamyka.
|
||||
- Zasób przychodowy ma `Kierunek == KierunekPartii.Przychód`. Zasób rozchodowy na stanie
|
||||
ujemnym ma `Kierunek == KierunekPartii.Rozchód` — nie myl ich przy sumowaniu stanu.
|
||||
- Nie modyfikuj `Zasob`/`Obrot` ręcznie — to tabele wyliczane przez moduł magazynowy.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W32 — Przetwarzanie obrotów faktury sprzedaży i dokumentu rozchodowego (`dok.Obroty`, `dok.ObrotyWszystkie`)
|
||||
|
||||
**Cel:** odczytać obroty magazynowe (ruchy) wygenerowane przez dokument — rozchód
|
||||
(FV/WZ/RW) lub przychód — w tym obroty z dokumentów zależnych.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Property | Co zwraca |
|
||||
|---|---|---|
|
||||
| Obroty związane bezpośrednio z dokumentem | `dok.Obroty` (`SubTable`) | dla przychodu: po stronie przychodowej; dla rozchodu: po stronie rozchodowej |
|
||||
| Wszystkie obroty (z dok. zależnymi, bez storna zasobu) | `dok.ObrotyWszystkie` (`ListWithView`) | obroty wszystkich powiązanych dok. magazynowych |
|
||||
| Obroty wszystkich pozycji | `dok.ObrotyWszystkiePozycji` (`ListWithView`) | po pozycjach (z pozycjami zależnymi) |
|
||||
| Z korektami, wg partii pierwotnej | `dok.ObrotyWszystkieWgPartiiPierwotnej` (`ListWithView`) | uwzględnia dok. korygujące |
|
||||
|
||||
**Pola i typy:** `Obrot`: `Ilosc: Quantity`, `Towar: Towar`, `Magazyn: Magazyn`,
|
||||
`Okres: OkresMagazynowy`, `Data: Date`, `Czas: Time`, `Korekta: KorektaObrotu`,
|
||||
`Stornowany: Obrot`, `Przychod: PartiaTowaru`, `Rozchod: PartiaTowaru`,
|
||||
`PrzychodPierwotny: PartiaTowaru`.
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
// dok — zapisana faktura sprzedaży / dokument rozchodowy (po session.Save())
|
||||
// 1) Obroty samego dokumentu (strona dobrana automatycznie wg kierunku magazynu):
|
||||
foreach (Obrot o in dok.Obroty)
|
||||
{
|
||||
// Przychod/Rozchod to subrow PartiaTowaru — wskazuje partię i dokument źródłowy
|
||||
GrupaDostaw partiaRozchodu = o.Rozchod.PartiaTowaru; // z której partii zszedł towar
|
||||
GrupaDostaw partiaPrzychodu = o.Przychod.PartiaTowaru; // partia przychodowa (źródło)
|
||||
Console.WriteLine($"{o.Towar.Kod} ilość={o.Ilosc} z partii={partiaPrzychodu?.Numer}");
|
||||
}
|
||||
|
||||
// 2) Wszystkie obroty łącznie z dokumentami magazynowymi powiązanymi z fakturą:
|
||||
foreach (Obrot o in dok.ObrotyWszystkie.Cast<Obrot>())
|
||||
{
|
||||
if (o.Korekta == KorektaObrotu.StornoZasobu) continue; // ObrotyWszystkie już to pomija
|
||||
// ... agregacja ilości/wartości
|
||||
}
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- `dok.Obroty` automatycznie dobiera stronę (przychodowa vs rozchodowa) na podstawie
|
||||
kierunku magazynowego dokumentu — nie filtruj jej ręcznie po kierunku.
|
||||
- `ObrotyWszystkie`/`ObrotyWszystkiePozycji`/`ObrotyWszystkieWgPartiiPierwotnej` zwracają
|
||||
`ListWithView` — iteruj przez `.Cast<Obrot>()`. Pomijają już obroty `StornoZasobu`.
|
||||
- Obroty pojawiają się **po `Session.Save()`** dokumentu, nie po `Commit()`.
|
||||
- `Przychod`/`Rozchod`/`PrzychodPierwotny` to **subrow `PartiaTowaru`**, nie rekord partii —
|
||||
do rekordu `GrupaDostaw` sięgaj przez `.PartiaTowaru`, do dokumentu źródłowego przez
|
||||
`.Dokument`, do pozycji przez `.PozycjaIdent`.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W33 — Odczyt stanu magazynowego towaru (magazyn / data) — `mag.Zasoby` z filtrem
|
||||
|
||||
**Cel:** wyliczyć aktualny stan towaru w danym magazynie (i ewentualnie okresie), bez
|
||||
otwierania konkretnego dokumentu — np. do walidacji dostępności przed rozchodem.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Indeks | Sygnatura |
|
||||
|---|---|---|
|
||||
| Stan towaru w magazynie | `mag.Zasoby.WgTowar[towar, okres, magazyn]` | zawęź serwerowo do magazynu i okresu |
|
||||
| Stan towaru we wszystkich okresach/magazynach | `mag.Zasoby.WgTowar[towar]` | szersze — sumuj ostrożnie |
|
||||
| Zasoby konkretnej partii | `mag.Zasoby.WgPartiaTowaruMagazyn[partia, magazyn, towar]` | gdy znamy `GrupaDostaw` |
|
||||
| Zasoby magazynu w okresie | `mag.Zasoby.WgMagazyn[magazyn, okres]` | przegląd całego magazynu |
|
||||
|
||||
**Pola i typy:** `mag.Zasoby: Zasoby` (tabela). Indeksy zwracają `SubTable<Zasob>`.
|
||||
`OkresMagazynowy` z `mag.OkresyMag` (patrz HANDEL-W39). Ilości to `Quantity`.
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
var mag = session.GetMagazyny();
|
||||
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
|
||||
var magazyn = mag.Magazyny.WgSymbol["F"];
|
||||
var okres = mag.OkresyMag.WgOkres[Date.Today]; // okres obejmujący dzień (patrz HANDEL-W39)
|
||||
|
||||
// Stan = suma ilości zasobów przychodowych pomniejszona o rozchodowe (stan ujemny)
|
||||
Quantity stan = new(0, towar.JednostkaMag.Symbol);
|
||||
foreach (Zasob z in mag.Zasoby.WgTowar[towar, okres, magazyn])
|
||||
{
|
||||
if (z.Kierunek == KierunekPartii.Przychód)
|
||||
stan += z.Ilosc;
|
||||
else if (z.Kierunek == KierunekPartii.Rozchód)
|
||||
stan -= z.Ilosc;
|
||||
}
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- **Nie ładuj całej tabeli `Zasoby` do pamięci** — zawsze zawężaj indeksem
|
||||
(`WgTowar[...]`, `WgMagazyn[...]`, `WgPartiaTowaruMagazyn[...]`). Patrz `safe-code.md` §6.
|
||||
- Ilości są typu `Quantity` (ilość + jednostka), nie `double` — operuj na `Quantity` i
|
||||
pilnuj zgodności jednostek (`z.Ilosc.Symbol`).
|
||||
- Stan „na dzień" zależy od okresu magazynowego — dla daty historycznej wybierz właściwy
|
||||
`OkresMagazynowy`, nie zawsze bieżący.
|
||||
- Towary **bez magazynu** (np. usługi „MONTAZ", „TRANSPORT" w Demo) nie mają zasobów —
|
||||
zapytanie zwróci pustą kolekcję.
|
||||
- W bazie Demo stan ujemny jest blokowany przy zapisie rozchodu — odczyt stanu służy do
|
||||
wcześniejszej walidacji, ale ostateczną kontrolę i tak wykona `Session.Save()`.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W34 — Wyszukiwanie partii magazynowych (`GrupaDostaw`) według cech
|
||||
|
||||
**Cel:** odnaleźć partię (`GrupaDostaw`) po numerze, towarze lub cesze (np. numer serii,
|
||||
data ważności zapisana jako cecha), zanim wskażemy ją przy rozchodzie.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Klucz / mechanizm | Uwaga |
|
||||
|---|---|---|
|
||||
| Po numerze + towarze | `mag.GrupyDostaw.WgNumer[numer, towar]` | klucz unikalny — pojedynczy rekord lub null |
|
||||
| Po numerze (zbiór) | `mag.GrupyDostaw.WgNumer[numer]` | zwraca `SubTable<GrupaDostaw>` |
|
||||
| Wszystkie partie towaru | `mag.GrupyDostaw.WgTowar[towar]` | partie danego towaru |
|
||||
| Po dacie | `mag.GrupyDostaw.WgData[data]` | indeks po `Data` |
|
||||
| Po cesze | `partie[(GrupaDostaw g) => warunek]` na indeksie | cecha musi być zdefiniowana |
|
||||
|
||||
**Pola i typy:** `GrupaDostaw`: `Numer: string` (`public virtual`, czasem nadawany
|
||||
automatycznie), `Towar: Towar`, `Data: Date`, `Blokada: bool`,
|
||||
`Features: FeatureCollection`, `KodKreskowy: string`. Klucz `WgNumer` = (`Numer`, `Towar`).
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
var mag = session.GetMagazyny();
|
||||
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
|
||||
|
||||
// 1) Partia po numerze i towarze — klucz unikalny:
|
||||
GrupaDostaw partia = mag.GrupyDostaw.WgNumer["LOT-2026-001", towar];
|
||||
|
||||
// 2) Wszystkie niezablokowane partie towaru — filtr serwerowy na indeksie:
|
||||
foreach (GrupaDostaw g in mag.GrupyDostaw.WgTowar[(GrupaDostaw g) => !g.Blokada])
|
||||
{
|
||||
// odczyt cechy zapisanej na partii (np. numer serii / data ważności):
|
||||
object seria = g.Features["NumerSerii"]; // cecha musi być wcześniej zdefiniowana
|
||||
}
|
||||
|
||||
// 3) Filtr po dacie powstania partii:
|
||||
foreach (GrupaDostaw g in mag.GrupyDostaw.WgData[Date.Today]) { /* ... */ }
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- `WgNumer[numer, towar]` zwraca **pojedynczy** rekord (może być `null`); `WgNumer[numer]`
|
||||
i `WgTowar[towar]` zwracają **zbiór** (`SubTable`).
|
||||
- W `RowCondition` używaj tylko **pól bazodanowych** (`Numer`, `Towar`, `Data`, `Blokada`).
|
||||
Pola kalkulowane (np. `KodKreskowy`) i wartości cech rzucą `LinqConditionException` —
|
||||
cechę filtruj dopiero po materializacji albo przez dedykowany warunek na cesze.
|
||||
- Cecha (`Features["…"]`) wymaga wcześniej zdefiniowanej definicji cechy — odwołanie do
|
||||
niezdefiniowanej cechy rzuca wyjątek (patrz `features.md`).
|
||||
- `Numer` partii bywa **nadawany automatycznie** (autonumerowanie wg karty towaru lub wg
|
||||
cechy) — nie zakładaj, że zawsze ustawisz go ręcznie.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W35 — Dokument rozchodowy ze wskazaniem JEDNEJ partii
|
||||
|
||||
**Cel:** wystawić rozchód (WZ/RW/FV), w którym pozycja schodzi z **konkretnej, wskazanej
|
||||
partii** — a nie z partii wybranej automatycznie przez algorytm magazynu.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Mechanizm | Uwaga |
|
||||
|---|---|---|
|
||||
| Wskazanie partii przez pozycję dostawy | `poz.Dostawa = pozycjaPrzyjęcia` | `Dostawa: PozycjaDokHandlowego` (pozycja PW/PZ) |
|
||||
| Wskazanie partii pierwotnej | `poz.DostawaPierwotna` | dla łańcucha korekt |
|
||||
| Tryb wskazania na definicji | `DefDokHandlowego.WskazaniePartii` | `WyborPartiiOpcje` (Dozwolony/Wymuszony…) |
|
||||
| Identyfikacja przez cechę | gdy magazyn `WgCechyPozycji` | partia wybierana wg cechy pozycji (HANDEL-W37, HANDEL-W39) |
|
||||
|
||||
**Pola i typy:** `poz.Dostawa: PozycjaDokHandlowego` (kategoria „Magazyn", opis „Pozycja
|
||||
dostawy dla danego rozchodu magazynowego"). Tryb sterowany przez
|
||||
`DefDokHandlowego.WskazaniePartii: WyborPartiiOpcje` (`Zabroniony=0`, `Dozwolony=1`,
|
||||
`Automatyczny=2`, `Wymuszony=4`, `WymuszonyDodawanie`, `WymuszonyZatwierdzanie`,
|
||||
`WgTowaru=8`).
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
var mag = session.GetMagazyny();
|
||||
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
|
||||
var magazyn = mag.Magazyny.WgSymbol["F"];
|
||||
|
||||
// WARUNEK WSTĘPNY: istnieje ZAPISANE przyjęcie (PW/PZ) tego towaru (Demo blokuje stan ujemny).
|
||||
// Znajdź pozycję przyjęcia odpowiadającą partii, z której chcemy zejść:
|
||||
GrupaDostaw partia = mag.GrupyDostaw.WgNumer["LOT-2026-001", towar];
|
||||
Obrot przychod = mag.Obroty.WgPrzychodPartiaTowaruMagazyn[partia, magazyn, towar]
|
||||
.Cast<Obrot>().FirstOrDefault();
|
||||
PozycjaDokHandlowego pozycjaPrzyjecia = przychod?.Przychod.Dokument?
|
||||
.Pozycje.Cast<PozycjaDokHandlowego>()
|
||||
.FirstOrDefault(p => p.Towar == towar);
|
||||
|
||||
using (var t = session.Logout(editMode: true))
|
||||
{
|
||||
var dok = new DokumentHandlowy();
|
||||
session.AddRow(dok);
|
||||
dok.Definicja = session.GetHandel().DefDokHandlowych.WgSymbolu["WZ"];
|
||||
dok.Magazyn = magazyn;
|
||||
|
||||
var poz = new PozycjaDokHandlowego(dok);
|
||||
session.AddRow(poz);
|
||||
poz.Towar = towar; // USTAW PIERWSZY
|
||||
poz.Ilosc = new Quantity(2, poz.Ilosc.Symbol);
|
||||
poz.Dostawa = pozycjaPrzyjecia; // WSKAZANIE JEDNEJ partii (dostawy)
|
||||
t.Commit(); // CommitUI() w workerze/extenderze
|
||||
}
|
||||
session.Save(); // tu nalicza się obrót/zasób rozchodowy
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- Wskazanie partii działa tylko, gdy definicja dokumentu na to pozwala
|
||||
(`WskazaniePartii != Zabroniony`). Przy `Zabroniony` partia jest dobierana wyłącznie
|
||||
algorytmem magazynu — ustawienie `poz.Dostawa` zostanie zignorowane lub odrzucone.
|
||||
- `poz.Dostawa` to **pozycja dokumentu przyjęcia** (`PozycjaDokHandlowego`), a nie rekord
|
||||
`GrupaDostaw`. Partię `GrupaDostaw` mapujesz na pozycję przyjęcia przez obrót przychodowy
|
||||
(`Obrot.Przychod.Dokument` + `PozycjaIdent`) — jak w snippetcie.
|
||||
- Demo blokuje stan ujemny: bez **zapisanego** przyjęcia tej partii `Session.Save()`
|
||||
rozchodu rzuci wyjątek (`StanUjemnyVerifier`).
|
||||
- Pozycje obu dokumentów muszą być w **tej samej sesji** — nie mieszaj rekordów z różnych
|
||||
sesji (`session.Get(...)`).
|
||||
- Ustaw `poz.Dostawa` **przed** `Commit()`; właściwy obrót zostaje naliczony dopiero w
|
||||
`Save()`.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W36 — Dokument rozchodowy ze wskazaniem WIELU partii
|
||||
|
||||
**Cel:** wystawić rozchód, którego ilość pochodzi z **kilku różnych partii** (np. 10 szt:
|
||||
6 z LOT-A, 4 z LOT-B) — każda partia jako osobna pozycja rozchodu wskazująca swoją dostawę.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Mechanizm | Uwaga |
|
||||
|---|---|---|
|
||||
| Pozycja per partia | po jednej `PozycjaDokHandlowego` na każdą wskazaną dostawę | najprostszy, czytelny |
|
||||
| Wybór przez worker dostaw | `IRelacjeService` + `HandlerSet.WybierzDostawyCallback` | dla relacji nadrzędny→podrzędny |
|
||||
| Automatyczny rozdział wg algorytmu | `WskazaniePartii = Automatyczny` | platforma sama dzieli na partie |
|
||||
|
||||
**Pola i typy:** jak HANDEL-W35 — wiele pozycji, każda z własnym `poz.Dostawa` i `poz.Ilosc`.
|
||||
Przy generowaniu z dokumentu nadrzędnego: `IRelacjeService.NowyPodrzednyIndywidualny(...)`
|
||||
z `HandlerSet { WybierzDostawyCallback = ... }` (namespace
|
||||
`Soneta.Handel.RelacjeDokumentow.Api`, wymaga `using Microsoft.Extensions.DependencyInjection;`).
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
var mag = session.GetMagazyny();
|
||||
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
|
||||
var magazyn = mag.Magazyny.WgSymbol["F"];
|
||||
|
||||
// Mapowanie: numer partii -> ilość do zejścia
|
||||
var rozdzial = new (string numer, double ilosc)[] { ("LOT-A", 6), ("LOT-B", 4) };
|
||||
|
||||
using (var t = session.Logout(editMode: true))
|
||||
{
|
||||
var dok = new DokumentHandlowy();
|
||||
session.AddRow(dok);
|
||||
dok.Definicja = session.GetHandel().DefDokHandlowych.WgSymbolu["WZ"];
|
||||
dok.Magazyn = magazyn;
|
||||
|
||||
foreach (var (numer, ilosc) in rozdzial)
|
||||
{
|
||||
GrupaDostaw partia = mag.GrupyDostaw.WgNumer[numer, towar];
|
||||
Obrot przychod = mag.Obroty.WgPrzychodPartiaTowaruMagazyn[partia, magazyn, towar]
|
||||
.Cast<Obrot>().FirstOrDefault();
|
||||
PozycjaDokHandlowego dostawa = przychod?.Przychod.Dokument?
|
||||
.Pozycje.Cast<PozycjaDokHandlowego>().FirstOrDefault(p => p.Towar == towar);
|
||||
|
||||
var poz = new PozycjaDokHandlowego(dok);
|
||||
session.AddRow(poz);
|
||||
poz.Towar = towar;
|
||||
poz.Ilosc = new Quantity(ilosc, poz.Ilosc.Symbol);
|
||||
poz.Dostawa = dostawa; // każda pozycja wskazuje INNĄ partię
|
||||
}
|
||||
t.Commit();
|
||||
}
|
||||
session.Save();
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- Każda wskazana partia = **osobna pozycja** rozchodu. Nie da się jedną pozycją wskazać
|
||||
dwóch różnych partii — `poz.Dostawa` to pojedyncza referencja.
|
||||
- Suma ilości wskazanych partii musi mieścić się w zapisanym stanie każdej partii
|
||||
(Demo blokuje stan ujemny per partia).
|
||||
- Przy generowaniu z dokumentu nadrzędnego (ZO→FV) wybór wielu dostaw realizuje
|
||||
`HandlerSet.WybierzDostawyCallback` — brak implementacji callbacku przy
|
||||
`WyborPozycjiDlaRelacji != BrakOkna` skutkuje `NotImplementedException`.
|
||||
- Wszystkie pozycje w jednej transakcji edycyjnej, zapis raz przez `Session.Save()`.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W37 — Dokument przyjęcia (PW/PZ) z numerem serii — zapis numeru serii jako cecha
|
||||
|
||||
**Cel:** zarejestrować przyjęcie towaru i zapisać **numer serii / partii**. Jeśli nie ma
|
||||
dedykowanego pola na serię, numer przenosimy jako **cechę** (`Features`) pozycji/dokumentu,
|
||||
skąd platforma przenosi go na partię (`GrupaDostaw`) i obrót.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Mechanizm | Uwaga |
|
||||
|---|---|---|
|
||||
| Numer partii wprost | `GrupaDostaw.Numer` | gdy partia jest tworzona/wskazywana jawnie |
|
||||
| Numer serii jako cecha pozycji | `poz.Features["NumerSerii"] = "..."` | przenoszony na partię/obrót |
|
||||
| Autonumerowanie wg cechy | `WyborPartiiAutonumerowanie.WgCechy` | numer partii brany z cechy |
|
||||
| Data ważności jako cecha | `poz.Features["DataWaznosci"] = date` | analogicznie do serii |
|
||||
|
||||
**Pola i typy:** `dok.Features["…"]` i `poz.Features["…"]`
|
||||
(`FeatureCollection`, indeksator po nazwie definicji cechy, zwraca/przyjmuje `object`).
|
||||
`GrupaDostaw.Numer: string`. Tryb numeracji partii:
|
||||
`WyborPartiiAutonumerowanie` (`Brak=0`, `Standardowe=1`, `WgCechy=2`).
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
var mag = session.GetMagazyny();
|
||||
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
|
||||
|
||||
using (var t = session.Logout(editMode: true))
|
||||
{
|
||||
var dok = new DokumentHandlowy();
|
||||
session.AddRow(dok);
|
||||
dok.Definicja = session.GetHandel().DefDokHandlowych.WgSymbolu["PW"]; // przyjęcie
|
||||
dok.Magazyn = mag.Magazyny.WgSymbol["F"];
|
||||
|
||||
var poz = new PozycjaDokHandlowego(dok);
|
||||
session.AddRow(poz);
|
||||
poz.Towar = towar;
|
||||
poz.Ilosc = new Quantity(10, poz.Ilosc.Symbol);
|
||||
poz.Cena = new DoubleCy(5m, poz.Cena.Symbol);
|
||||
|
||||
// Numer serii jako cecha pozycji — przeniesiony na partię/obrót po Save:
|
||||
poz.Features["NumerSerii"] = "LOT-2026-001"; // definicja cechy musi istnieć
|
||||
t.Commit();
|
||||
}
|
||||
session.Save();
|
||||
|
||||
// Po zapisie partia jest dostępna w GrupyDostaw; numer serii odczytasz z cechy partii:
|
||||
GrupaDostaw partia = mag.GrupyDostaw.WgTowar[towar].Cast<GrupaDostaw>()
|
||||
.FirstOrDefault(g => Equals(g.Features["NumerSerii"], "LOT-2026-001"));
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- Cecha musi być **wcześniej zdefiniowana** (`FeatureSetDefinition`) i — by przenosiła się
|
||||
na partię — odpowiednio skonfigurowana w module magazynowym. Odwołanie do niezdefiniowanej
|
||||
cechy rzuca wyjątek.
|
||||
- Partia powstaje dopiero **po `Session.Save()`** przyjęcia — przed zapisem
|
||||
`mag.GrupyDostaw` jej nie zawiera.
|
||||
- Gdy magazyn ma autonumerowanie `WgCechy`, `GrupaDostaw.Numer` jest **wyliczany z cechy** —
|
||||
nie ustawiaj go ręcznie sprzecznie z cechą.
|
||||
- Filtr partii po wartości cechy rób **po materializacji** (jak w snippetcie) — wartości
|
||||
cech nie są polami bazodanowymi, więc nie wejdą do `RowCondition`.
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W38 — Odczyt rozchodu zasobów: powiązanie pozycji rozchodu z partią pierwotną / przyjęciem
|
||||
|
||||
**Cel:** dla pozycji/obrotu rozchodowego ustalić, **z której partii (i którego przyjęcia)**
|
||||
zszedł towar — np. do raportu pochodzenia (traceability) lub rozliczenia kosztu.
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Źródło | Co zwraca |
|
||||
|---|---|---|
|
||||
| Partia rozchodu | `obrot.Rozchod.PartiaTowaru` | `GrupaDostaw` strony rozchodowej |
|
||||
| Partia przychodowa (źródłowa) | `obrot.Przychod.PartiaTowaru` | partia, z której zszedł towar |
|
||||
| Partia pierwotna | `obrot.PrzychodPierwotny.PartiaTowaru` | pierwotne przyjęcie (przed korektami) |
|
||||
| Dokument/pozycja źródłowa | `obrot.Przychod.Dokument`, `.PozycjaIdent` | przyjęcie i jego pozycja |
|
||||
| Dostawa na pozycji rozchodu | `poz.Dostawa`, `poz.DostawaPierwotna` | pozycja przyjęcia powiązana z rozchodem |
|
||||
|
||||
**Pola i typy:** subrow `PartiaTowaru` na `Obrot`/`Zasob`:
|
||||
`Dokument: DokumentHandlowy`, `PozycjaIdent: int`, `PartiaTowaru: GrupaDostaw`,
|
||||
`KontrahentPartii: Kontrahent`, `Data: Date`, `Czas: Time`, `Typ: TypPartii`,
|
||||
`Wartosc: decimal`. Na pozycji: `poz.Dostawa: PozycjaDokHandlowego`,
|
||||
`poz.DostawaPierwotna: PozycjaDokHandlowego`.
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
// dok — zapisany dokument rozchodowy (FV/WZ/RW)
|
||||
foreach (Obrot o in dok.Obroty)
|
||||
{
|
||||
// Strona rozchodowa = partia, z której zeszła ilość:
|
||||
GrupaDostaw partiaRozchodu = o.Rozchod.PartiaTowaru;
|
||||
|
||||
// Strona przychodowa = przyjęcie, z którego pochodzi towar (pochodzenie):
|
||||
DokumentHandlowy przyjecie = o.Przychod.Dokument;
|
||||
GrupaDostaw partiaZrodlowa = o.Przychod.PartiaTowaru;
|
||||
|
||||
// Pierwotne przyjęcie (przed łańcuchem korekt):
|
||||
GrupaDostaw partiaPierwotna = o.PrzychodPierwotny.PartiaTowaru;
|
||||
|
||||
Console.WriteLine(
|
||||
$"{o.Towar.Kod} ilość={o.Ilosc} z przyjęcia={przyjecie?.Numer} " +
|
||||
$"partia={partiaZrodlowa?.Numer} kontrahent={o.Przychod.KontrahentPartii?.Kod}");
|
||||
}
|
||||
|
||||
// Powiązanie na poziomie pozycji rozchodu:
|
||||
foreach (PozycjaDokHandlowego poz in dok.Pozycje)
|
||||
{
|
||||
PozycjaDokHandlowego pozycjaPrzyjecia = poz.Dostawa; // pozycja PW/PZ
|
||||
}
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- Rozróżniaj `Przychod` (źródło, czyli przyjęcie), `Rozchod` (bieżący rozchód) i
|
||||
`PrzychodPierwotny` (źródło sprzed korekt). Do raportu pochodzenia używaj `Przychod`/
|
||||
`PrzychodPierwotny`.
|
||||
- `obrot.Przychod`/`Rozchod` to **subrow `PartiaTowaru`** — nie jest `null` jako struktura,
|
||||
ale jego pola (np. `PartiaTowaru`, `Dokument`) mogą być puste dla prostej ewidencji bez
|
||||
partii. Zabezpiecz odczyt `?.`.
|
||||
- Jedna pozycja rozchodu może wygenerować **wiele obrotów** (gdy zeszła z kilku przychodów,
|
||||
np. FIFO) — iteruj po obrotach, nie zakładaj relacji 1:1 pozycja↔partia.
|
||||
- Odczyt sensowny dopiero **po `Session.Save()`** dokumentu (przed zapisem brak obrotów).
|
||||
|
||||
---
|
||||
|
||||
### HANDEL-W39 — Odczyt okresów magazynowych i kontekstu wyceny (FIFO/LIFO/wg dostaw)
|
||||
|
||||
**Cel:** ustalić aktywny okres magazynowy dla daty oraz dowiedzieć się, jakim algorytmem
|
||||
magazyn wycenia rozchód (co decyduje o wyborze partii, gdy nie wskazujemy jej ręcznie).
|
||||
|
||||
**Warianty:**
|
||||
|
||||
| Wariant | Źródło | Uwaga |
|
||||
|---|---|---|
|
||||
| Okres dla daty | `mag.OkresyMag.WgOkres[data]` | klucz po `Okres.To` |
|
||||
| Czy okres zamknięty | `okres.Zamkniety: bool` | zamknięcie blokuje modyfikacje |
|
||||
| Algorytm rozchodu magazynu | `magazyn.Algorytm: AlgorytmMagazynowy` | FIFO/LIFO/wg dostaw/wg cechy |
|
||||
| Cecha algorytmu (wg cechy) | `magazyn.CechaAlgorytmu: string` | nazwa cechy pozycji/dokumentu |
|
||||
|
||||
**Pola i typy:** `OkresMagazynowy`: `Okres: FromTo`, `Zamkniety: bool`. Tabela `OkresyMag`,
|
||||
indeks `WgOkres` (po `Okres.To`). `Magazyn.Algorytm: AlgorytmMagazynowy` (`FIFO=0`,
|
||||
`LIFO=1`, `NieLiczyćStanów=2`, `WgDostawy=3`, `WgDostawyPrzyZatwierdzaniu=10`,
|
||||
`OdNajdroższych=4`, `OdNajtańszych=5`, `WgCechyPozycji=6/7`, `WgCechyDokumentu=8/9`),
|
||||
`Magazyn.CechaAlgorytmu: string`.
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
var mag = session.GetMagazyny();
|
||||
var magazyn = mag.Magazyny.WgSymbol["F"];
|
||||
|
||||
// Okres magazynowy obejmujący wskazaną datę:
|
||||
OkresMagazynowy okres = mag.OkresyMag.WgOkres[Date.Today];
|
||||
bool zamkniety = okres != null && okres.Zamkniety;
|
||||
|
||||
// Kontekst wyceny rozchodu (jak magazyn dobiera partie automatycznie):
|
||||
AlgorytmMagazynowy algorytm = magazyn.Algorytm;
|
||||
bool rozchodWgCechy =
|
||||
algorytm is AlgorytmMagazynowy.WgCechyPozycji or AlgorytmMagazynowy.WgCechyPozycjiMalejąco
|
||||
or AlgorytmMagazynowy.WgCechyDokumentu or AlgorytmMagazynowy.WgCechyDokumentuMalejąco;
|
||||
|
||||
string cechaWyceny = rozchodWgCechy ? magazyn.CechaAlgorytmu : null;
|
||||
|
||||
string opisWyceny = algorytm switch
|
||||
{
|
||||
AlgorytmMagazynowy.FIFO => "rozchód od najstarszych dostaw",
|
||||
AlgorytmMagazynowy.LIFO => "rozchód od najnowszych dostaw",
|
||||
AlgorytmMagazynowy.WgDostawy => "rozchód wg wskazanej dostawy (partii)",
|
||||
_ => algorytm.ToString()
|
||||
};
|
||||
```
|
||||
|
||||
**Pułapki:**
|
||||
- Gdy magazyn liczy `WgDostawy` (wskazanie partii) lub `WgCechy*`, automatyczny dobór partii
|
||||
zależy od `poz.Dostawa` (HANDEL-W35/HANDEL-W36) lub cechy (`CechaAlgorytmu`) — bez nich rozchód nie
|
||||
zostanie poprawnie rozliczony.
|
||||
- `NieLiczyćStanów` oznacza, że magazyn **nie prowadzi zasobów** — `dok.Zasoby` pozostanie
|
||||
puste, a kontroli stanu ujemnego nie ma.
|
||||
- Modyfikacja dokumentów w **zamkniętym** okresie (`okres.Zamkniety == true`) zostanie
|
||||
odrzucona — sprawdź to przed edycją wstecz.
|
||||
- `OkresMagazynowy` to dane konfiguracyjne (`config="true"`, `guided`) — nie twórz okresów
|
||||
„w locie" w kodzie operacyjnym; korzystaj z istniejących.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user