Kruczki i sztuczki

Alternatywny, uniwersalny eksport pliku DBF na plik tekstowy [friko]
Opis: FoxPro udostępnia polecenie copy, które umożliwia eksport pliku DBF (tabeli bazy danych) do plików o innych formatach, w tym do formatu tekstowego (m.in. rozdzielanego przecinkiem lub tabulatorem). Wśród tej różnorodności formatów nie ma jednak popularnego formatu CSV (rozdzielanego średnikiem). Bywa też, że pojawia się potrzeba eksportu do pliku tekstowego z niestandardowym separatorem.

Poniżej podaję sposób na uniwersalny eksport dowolnej tabeli (pliku DBF) do pliku tekstowego. Niestety jego mankamentem jest zdecydowanie mniejsza wydajność w stosunku do natywnego copy.

procedure Export
 parameters p_FName, p_Sprtr && nazwa pliku, separator
 private all like l_*
 l_cnt=fcount() && ustalamy liczbę kolumn tabeli
 l_EOLn=Chr(13)+Chr(10) && zmienna przechowująca znaki końca wiersza i przejścia do nowej linii
 set textmerge on to (p_FName) noshow && włączenie łączenia tekstu do pliku
 scan
  for l_=1 to l_cnt-1 && przetwórz wszystkie kolumny oprócz ostatniej
   \\<<Eval(Field(l_))>><<p_Sprtr>>
  endfor && teraz jeszcze ostatnia kolumna i wstawienie końca wiersza
  \\<Eval(Field(l_cnt))>><<l_EOLn>>
 endscan
 set textmerge to && wyłączenie łączenia tekstu do pliku
return
Konwersja plików tekstowych. [friko]
Opis: Zdarza się, że konieczna jest konwersja pliku tekstowego z jednego systemu kodowania na inny. Poniżej podaję procedurę, która dokonuje takiej operacji. Pierwszym jej parametrem jest nazwa pliku tekstowego, który ma zostać przekonwertowany. Po zakończeniu procedury plik ten zawiera tekst po konwersji (WAŻNE - nie jest tworzona kopia pliku). Pozostałe dwa parametry oznaczają odpowiednio: numer strony kodowej, w której są zakodowane znaki narodowe w pliku; drugi numer strony kodowej, w której mają być zakodowane znaki narodowe w wyniku konwersji (lista numerów tutaj). Możliwa jest też inna postać tych parametrów. Można w nich podać listę znaków, które mają zostać zamieniony na znaki podane w parametrze ostatnim. Zamiana następuje na zasadzie: pierwszy znak w parametrze p_DstCP zastępuje pierwszy znak w parametrze p_SrcCP, itd. Długości p_SrcCP i p_DstCP muszą być takie same!

Jeśli procedura zakończy swoje działanie prawidłowo, zwraca wartość .T., w przeciwnym wypadku .F.


procedure Convert
 parameters p_FName, p_SrcCP, p_DstCP
 private all like l_*
 && jeśli plik nie istnieje lub typy parametrów nie są zgodne
 if not file(p_FName) or type('p_DstCP')<>type('p_SrcCP') 
  return .F. && zakończ informując o niepowodzeniu
 endif.
 l_native=type('p_SrcCP')='N' && czy konwersja ma się odbyć za pomocą CPConvert?
 && jeśli parametry konwersji zawierając ciągi znaków porównaj ich długości
 if not l_native and len(p_SrcCP)<>len(p_DstCP)
  return .F. && zakończ informując o niepowodzeniu
 endif
 l_file=FOpen(p_FName,2) && otwórz plik w trybie do odczytu i zapisu
 do while .T.
  l_buf=fread(l_file,2048) && odczytaj 2kB z pliku
  l_len=len(l_buf)
  if l_len=0 && jeśli bufor jest pusty - nie ma już co konwertować
   exit && opuść pętle
  endif
  =fseek(l_file,-l_len,1) && cofnij się o długość bufora
  if l_native
   l_buf=CPConvert(p_SrcCP,p_DstCP,l_buf) && dokonaj konwersji natywną funkcją FoxPro
  else
   l_buf=ChrTran(l_buf,p_SrcCP,p_DstCP) && dokona konwersji wg ciągów znaków
  endif
  =fwrite(l_file,l_buf) && zapisz zmodyfikowany bufor
 enddo
 =fclose(l_file)
return .T.
Możliwość wydruku z Subiekta wysokiej jakości etykiet towarowych do umieszczenia na regałach [friko]
Opis: Jak wszyscy wiedzą środowisko Dos nie umożliwia w prosty sposób tworzenia wyszukanych wydruków i dlatego etykiety z Subiekta mają niezbyt oszałamiającą postać. Nieosiągalne wydaje się być uzyskanie takich efektów, jakie oferuje program Word. Ale czy rzeczywiście nieosiągalne ? Jeśli przyjrzeć się bliżej edytorowi Word okazuje się, że ma on mechanizmy współpracy z innymi programami, pozwalające zaprząc go do pracy, z którą te programy poradzić sobie nie mogą. Warunkiem jest jedynie umiejętność otrzymania i przekazania do Worda źródła danych, które ma on wydrukować. Proszę zauważyć, że Subiekt daje taką możliwość. W menu Cennik jest opcja umożliwiająca eksport danych do programu Word celem ładniejszego wydruku. Oczywiście dane te trzeba jeszcze odpowiednio wprowadzić do Worda, sformatować i dokonać być może innych działań - niemniej potwierdza to fakt, że oba programy mogą ze sobą współpracować.

Prezentowane tutaj rozwiązanie demonstruje jak doprowadzić do współpracy programów Subiekt i Word, w celu uzyskania wydruku etykiet wysokiej jakości.
Jeśli dodatkowo Subiekt uruchamiany jest pod kontrolą Windows (w oknie MS-Dos - także w trybie pełnoekranowym) przekaże on automatycznie sterowanie do programu Word.
Aby współpraca obu programów była poprawna, na komputerze należy zainstalować Worda co najmniej w wersji 97 SR-1 (patrz menu Worda: Pomoc -> Microsoft Word - informacje), wraz ze sterownikiem ODBC dla plików tekstowych.
W przeciwnym wypadku Word podczas otwierania dokumentu METKI.DOC będzie komunikował o niemożliwości odnalezienia źródła danych korespondecji seryjnej.

Przykładowe etykiety zawierają następujące informacje o towarze: nazwa towaru, cena (zgodna z ceną ustaloną dla kasy fiskalnej z poziomu Administrator -> ECR/POS -> Różne -> Cena sprzedaży), kod kreskowy, kod dostawcy oraz przykładowe logo (logo witryny Subiektyw). Wygląd etykiet zawarty jest w pliku METKI.DOC, który jest dokumentem korespondencji seryjnej edytora Word (szablonem). Zmianę wyglądu etykiem można uzyskać poprzez modyfikację jego zawartości.

Poniżej przedstawiony jest kod źródłowy programu przygotowującej dane dla programu Word. Jego działanie polega na utworzeniu pliku tekstowego ETYKIETY.TXT - źródła korespondencji seryjnej dokumentu METKI.DOC, zawierającego wspomniane wyżej informacje o towarze, oddzielone od siebie średnikiem i umieszczone w kolejnych liniach pliku oprócz pierwszej, która zawiera informacje nagłówkowe tj. nazwy pól, wg których METKI.DOC umieszcza dane na etykietach.

Po utworzeniu pliku ETYKIETY.TXT i umieszczeniu go w katalogu SUBIEKT4\SYSTEM program wywołuje program Word:

     start winword metki /mDoDok

i przekazuje sterowanie do niego. Katalogiem roboczym Worda jest SUBIEKT4\SYSTEM i tam dokument korespondencji seryjnej szuka źródła danych (ETYKIETY.TXT). Umieszczone w METKI.DOC makro DoDok tworzy dodatkowy dokument programu Word, który można albo zapisać albo wydrukować.
METKI.DOC korzysta z zewnętrznego pliku LOGO.JPG zawierającego obrazek, który jest drukowany na każdej etykiecie. Musi być on umieszczony w tym samym katalogu. Zatem w przypadku chęci zapisania wygenerowanego dokumentu na dysk należy pamiętać, by do tego samego folderu został także skopiowany plik LOGO.JPG. Oczywiście METKI.DOC można dowolnie modyfikować, lub zastąpić go własnym (należy stworzyć w nim makro DoDok lub przekopiować je z METKI.DOC).

Uwaga !!! W trakcie uruchamiania programu Word może pojawić się następujący komunikat (o ile mamy włączoną opcję ostrzeganie o makrach w dokumentach Worda, co gorąco polecam):

Nie należy się jednak obawiać, dokument nie zawiera wirusa, a jedynie makro generujące dokument z etykietami gotowymi do wydruku. Można więc śmiało użyć przycisku Włącz makra.
Nieufni mogą (równie dobrze) kazać wyłączyć makra, z tym że później będą musieli użyć przycisków korespondencji seryjnej, by uzyskać wydruk.

Jeśli z jakiś przyczyn nie jest możliwe uruchamianie Subiekta pod kontrolą Windows, można uzyskać wydruk w inny sposób. Podczas pracy z Subiektem należy jedynie wybrać produkty i po uzyskaniu komunikatu o przygotowaniu etykiet do wydruku zakończyć pracę z nim pracę. Następnie należy uruchomić system Windows, na Pulpicie utworzyć skrót do programu Word, który uruchomi go z dwoma parametrami: metki.doc /mDoDok. Katalogiem roboczym skrótu powinien być: SUBIEKT4\SYSTEM. Powyższy skrót pozwoli uzyskać przygotowany wcześniej wydruk.

Aby móc skorzystać z opisywanego rozwiązania, jak również mieć możliwość modyfikacji dokumentu korespondencji seryjnej METKI.DOC zapoznaj się z warunkami użytkowania. Pliki: METKI.DOC oraz LOGO.JPG można pobrać z Ładowni

Poniżej źródło modułu:

procedure Etykiety
 private all like l_*
 if not (s4open('towary') and s4open('ecr_sys'))
  do s4message with 'Nie można otworzyć bazy danych'
  return
 endif
 store '' to l_wyb, l_dane
 if s4rpprms(" Wybierz towary do etykiet ","T:'','l_wyb','l_dane'")
  select ecr_sys
  go top
  if wykonaj(l_wyb, l_dane,;
     'tw_cena'+iif(between(es_cena,1,3),Str(es_cena,1),'2')+'b')
   do s4wait with 1
   run 'start winword metki /mDoDok'
   do s4wait
   do s4message with "Towary zostały przygotowane do wydruku etykiet."
  else
   do s4message with "Nie wybrano żadnych towarów"
  endif
 endif
 =s4close('ecr_sys')
return

procedure Wykonaj
 parameters p_wyb, p_dane, p_pole
 private all like l_*

 do s4wait with 3
 do case
  case p_wyb <> 3
   select StrTran(tw_nazwa,'.','. ') as nazwa, tw_kodpask as barkod,;
          tw_dostkod as symbol, &p_pole as cena;
    from towary;
    where (p_wyb = 1 OR (p_wyb = 2 AND tw_grupa = p_dane));
     and tw_status=' ';
     order by tw_nazwa;
    into cursor C1
  otherwise
   if reccount(p_dane)>0
    select StrTran(tw_nazwa,'.','. ') as nazwa, tw_kodpask as barkod,;
           tw_dostkod as symbol, &p_pole as cena;
     from towary;
      where tw_id in (select tw_id from &p_dane);
     order by tw_nazwa;
     into cursor C1
   endif
  endcase
 l_druk=_tally>0
 if l_druk
  do Zapisz with 'ETYKIETY.TXT'
 endif
 use in (p_dane)
 use in C1
 do s4wait
return l_druk

procedure Zapisz
 parameters p_FName
 set textmerge on to (p_FName) noshow
 \\Nazwa;Barkod;Symbol;Cena
 scan
 \"<<AllTrim(nazwa)>>";"<<AllTrim(barkod)>>";"<<AllTrim(symbol)>>";<<cena>>
 endscan
 set textmerge to
return
TFLDS.DBF - sposób na rozszerzenie listy towarów Subiekta. [friko]
Opis: Jak powszechnie wiadomo zawartość listy towarów w Subiekcie może być dowolnie kształtowana za pomocą umieszczonej w module Adminstarotor, w menu Towary opcji Listy. Cztery kolumny widoczne na liście towarów mogą być w tym miejscu wybrane spośród 27 dostępnych kolumn.

Mniej powszechna jest natomiast wiedza, że liczba owych - dostępnych kolumn może być dowolnie zwiększana. Aby tego dokonać należy dysponować jakimkolwiek edytorem baz danych, który pozwala dodawać rekordy do tabel DBF oraz dokumentacją techniczną opisującą format tabel używanych przez Subiekta (przede wszystkim tabeli TOWARY.DBF, w której przechowywane są informacje o towarach). Dokumetacja ta znajduje się na stronach producenta Subiekta - firmy Insert (www.insert.com.pl))

Następnie wystarczy zmodyfikować tabelę TFLDS umieszczoną w katalogu SUBIEKT4\SYSTEM, dodając na jej końcu pusty rekord i wypełnić przynajmniej dwa jego pola: TF_OPIS i TF_NAME. Pierwsze definiuje nazwę kolumny (jej nagłówek), drugie jej zawartość. Poniżej podaję przykład na uzyskanie dodatkowej kolumny o nazwie Narzut 1 zawierającej wartość narzutu dla pierwszej ceny towaru (domyślnie cena hurtowa).

TF_OPIS: Narzut 1
TF_NAME: TW_NARZUT1

Jak się okazuje zawartość pola TF_NAME jest traktowana przez Subiekta w sposób szczególny tzn. tak jakby to był fragment jego własnego kodu (a właściwie wyrażenia matematycznego). Mamy zatem de facto możliwość zmodyfikowania zachowania się Subiekta. Najprostszą konsekwencją tego faktu jest możliwość uzyskania podwójnej kolumny np. zawierającej cenę i stan towaru (dzięki czemu pozostałe trzy mogą zawierać np. nazwę towaru, kod towaru i kod dostawcy). W ten sposób uzyskamy jak gdyby 5 kolumn na liście towarów.
A oto sposób na uzyskanie takiego efektu:

TF_OPIS: Cena1b(Stan)
TF_NAME: CenaStan=Str(tw_cena1b,8,2)+'('+Str(stan.st_stan,5,0)+')'

Zawarty w polu TF_NAME fragment kodu realizuje przetworzenie na tekst liczby zawartej w polu TW_CENA1B tabeli TOWARY (będzie ona zajmować 8 znaków z czego 2 znaki zajmie wartość groszy) oraz przetworzenie na tekst wartości zapasu towaru przechowywanej w tabeli STAN (ponieważ domyślną tabelą jest TOWARY, to pole ST_STAN musi być poprzedzone nazwą tabeli, która go zawiera). Obydwa teksty są następnie łączone ze sobą, a zapas towaru jest dodatkowo otaczany nawiasami. Wszystko to przyporządkowywane jest zmiennej CenaStan, co jest niezbędne do prawidłowego zaprezentowania wartości wyrażenia (tylko zmienne mogą być wyświetlane, w poprzednim przykładzie TW_NARZUT1 też było zmienną). Oczywiście użycie pola z innej tabeli jest możliwe tylko dlatego, że Subiekt podczas wyświetlania listy towarów wiąże ze sobą te dwie tabele (dzięki temu danemu towarowi odpowiada jego stan).

Wiedząc, że TF_NAME zawiera kod wyrażenia, możemy pójść jeszcze dalej i wykorzystać np. funkcję FoxPro:

 iif(warunek, ta_wartość_gdy_warunek_prawdziwy, ta_wartość_gdy_warunek_fałszywy)

która pozwoli nam np. wyświetlić na liście towarów cenę zależną od aktualnie wybranego magazynu. Jeśli będzie to magazyn pierwszy, wówczas będzie to pierwsza cena, w przeciwnym wypadku cena druga.

TF_OPIS: CenaWgMag
TF_NAME: Oblicz=iif(x_MagID=1, tw_cena1b, tw_cena2b)

ponieważ pierwszy parametr funkcji iff to także wyrażenie, możemy więc wstawić tam również funkcję. Oto sposób na uzyskanie dwóch różnych cen w zależności od stanu klawisza Num Lock, jeśli jest włączony wówczas widoczna jest cena pierwsza, gdy wyłączony cena 2. Efekt jest widoczny od razu na liście, pod warunkiem, że przewiniemy ją w dół lub w górę.

TF_OPIS: CenaWgMag
TF_NAME: Oblicz=iif(NumLock(),tw_cena1b,tw_cena2b)

Zwieńczeniem powyższych rozważań jest użycie jako wyrażenia, funkcji napisanej w FoxPro. Niech będzie to funkcja pozwalająca dowolnie zmieniać zawartość kolumny, wypełniając ją np. wybraną ceną sprzedaży. Tym razem pola tabeli TFLDS.DBF będą miały następującą postać:

TF_OPIS: Cena
TF_NAME: Oblicz=Pole()

Funkcja Pole ma następującą postać:

function Pole
 if Empty(On('key','F12')) && jeśli nie zaprogramowano dotąd klawisza F12
  do Ini                   && dokonaj tego i zainicjuj wybór zawartości kolumny
 endif
return Eval(ps_Pole)       && zwróć domyślną zawartość kolumny

procedure Wybor
 private all like l_*
 push key clear            && zapamiętaj przyporządkowania klawiszy i wyczyść je
 define popup WyborM from 0,65 shadow margin
 define bar 1 of WyborM prompt 'Cena 1 N'   && przykładowa lista
 define bar 2 of WyborM prompt 'Cena 2 N'   && pól do wyboru
 define bar 3 of WyborM prompt 'Cena 3 N'
 define bar 4 of WyborM prompt 'Cena 1 B'
 define bar 5 of WyborM prompt 'Cena 2 B'
 define bar 6 of WyborM prompt 'Cena 3 B'
 define bar 7 of WyborM prompt 'Narzut 1'
 define bar 8 of WyborM prompt 'Narzut 2'
 define bar 9 of WyborM prompt 'Narzut 3'
 on selection popup WyborM do Zmien        && w przypadku wyboru uruchom Zmien
 activate popup WyborM                     && wyświetl menu
 release popup WyborM                      && i usuń je po opuszczeniu
 pop key                  && przywróć poprzednie przyporządkowanie klawiszy
return

procedure Zmien           && po wybraniu opcji w menu
 ps_Pole='tw_'+ChrTran(prompt(),' ','')  && ustal nową zawartość kolumny
 keyboard '{ESC}{Ctrl+Q}' && wyjdź z menu i zmień ustawienia browse
return                    && powyższe działa tylko, gdy set s4buforkl=2 (co najmniej)

procedure Ini             && Inicjowanie mechanizmu wyboru zawartości kolumny
 on key label F12 do Wybor in Pole       && gdy F12 wykonaj Wybor z POLE.FXP
 if type('ps_Pole')='U'   && jeśli do tej pory nie zdefiniowano zmiennej
  public ps_Pole          && uczyń ją zmienną publiczną
  ps_Pole='tw_cena1b'     && i zainicjuj domyślną wartością
 endif
return

Powyższą funkcję po skompilowaniu do postaci POLE.FXP (docelowo poleceniem compile pole nodebug - zajmuje mniej pamięci) należy umieścić w katalogu SUBIEKT4\SYSTEM, tam bowiem będzie jej szukał Subiekt.

Aby skorzystać z powyższego rozwiązania zapoznaj się z warunkami użytkowania.
Gotowa do użycia funkcja POLE.FXP znajduje się w Ładowni

AutoExec w Subiekcie. [friko]
Opis: Zestawienia własne dołączane do Subiekta są programami (typu FXP lub APP) napisanymi w FoxPro. Można je używać (uruchamiać) z poziomu modułu Szef (a od wersji Subiekta 4 1.08 także w module Sprzedawca).

Czy jest to jednak jedyne miejsce ? Na pierwszy rzut oka może się wydawać, że tak, albowiem nigdzie indziej firma Insert nie udostępniła takiej możliwości. Jednakże każdy w miarę zorientowany programista FoxPro szybko znajdzie zastosowanie dla polecenia on key label i po przyporządkowaniu do dowolnego klawisza swojej procedury, będzie mógł ją wywoływać ze zdecydowanie większej ilości miejsc programu (choć nie wszędzie, co wynika z idącego w parze z powyższym poleceniem, polecenia push key clear).

Niestety aby móc przyporządkować procedurę pod dany klawisz niezbędne jest wydanie polecenia on key label. Sprowadza się to zatem do uruchomienia jakiegoś zestawienia własnego, a to na dłuższą metę mija się z celem.

Najlepiej, gdyby Subiekt sam wykonywał za nas to polecenie (lub grupę poleceń). Ujmując rzecz inaczej, wspaniale byłoby, gdyby posiadał coś na kształ pliku AUTOEXEC.BAT z systemu Dos lub folderu AutoStart systemu Windows. Niestety autorzy programu z naturalnych względów nie przewidzieli takiej potrzeby.

Czy oznacza to, że nie ma szans na uzyskanie funkcjonalności AutoExec w Subiekcie ? Okazuje się, że jest - muszę jednak przyznać, iż jej odnalezienie zajęło mi pół roku.

Pierwszą myślą jaka przyszła mi do głowy było to, że Subiekt jako program napisany w FoxPro powinien korzystać z pliku CONFIG.FP - być może w nim istnieje jakaś furtka pozwalająca zrealizować funkcję AutoExec. Subiekt rzeczywiście korzysta z tego pliku, zaś w systemie pomocy FoxPro znajdowała się informacja, że umieszczenie w CONFIG.FP linii z komendą COMMAND=ZrobTo.APP uruchomi program ZrobTo.APP. Okazało się jednak, że ta funkcjonalność FoxPro jest dostępna tylko podczas pracy w jego środowisku. Programy wykonywane poza nim ignorują powyższy wpis. To samo dotyczyło komendy SHELL. Wyglądało na to, że AutoExec w Subiekcie jest niemożliwy do uzyskania.

W związku z tym odłożyłem ten temat na później i co jakiś czas wracałem do niego próbując różnych rozwiązań.
Właśnie podczas jednej z takich prób, kiedy testowałem wewnętrzne zmienne FoxPro (te zaczynające się od podkreślenia) poprzez ich ustawianie w CONFIG.FP, z systemu pomocy wyczytałem, że istnieje grupa czterech zmiennych _GEN* umożliwiająca zmianę pewnych funkcjonalności FoxPro (np. programu do generowania menu). Właśnie jedna z tych zmiennych o nazwie _GENPD okazała się długo szukaną furtką. Wystarczyło tylko umieścić ją w CONFIG.FP:

_GENPD=AUTOEXEC.FXP

aby uzyskać zamierzony efekt.

Jak się okazuje zmienna ta służy do określania nazwy sterownika drukarki, którego FoxPro ma używać do wykonywania wydruków. Jest ona o tyle specyficzna, że nadanie jej wartości (nazwy pliku sterownika), powoduje dodatkowo jego uruchomienie w celach inicjalizacji. Jest to proces, który zachodzi zarówno w środowisku FoxPro, jak również w programach zewnętrznych (a więc i Subiekcie).
Ponieważ FoxPro traktuje AutoExec.FXP, jak sterownik drukarki, będzie go zatem próbował wykorzystywać podczas drukowania. Dlatego zaraz po uruchomieniu, procedura AutoExec powinna wyczyścić zmienną _GENPD:

procedure AutoExec
 parameters p_1,p_2 && wg dokumentacji dwa parametry
 _GENPD='' && anulowanie wpisu z CONFIG.FP - nas tu nie było 
&& tutaj dowolne dalsze operacje
return

Wg dokumentacji, procedura sterownika powinna posiadać dwa parametry, stąd choć nie są one potrzebne procedurze AutoExec muszą zostać zadeklarowane.
Należy zdawać sobie sprawę, że _GENPD jest wykonywane jeszcze w czasie prologu FoxPro, w momencie, kiedy się ono inicjuje. Oznacza to, że Subiekt nie został jeszcze w ogóle "zauważony", dopiero za chwilę, kiedy skończą się czynności inicjalizacyjne zacznie być wykonywany kod Subiekta. W związku z tym, próby wywoływania procedur Subiekta dostępnych w zestawieniach własnych, mogą się nie powieść. Nic nie stoi natomiast na przeszkodzie, aby uruchomić wspomnianą wyżej komendę on key label lub ustawić swoją własną zmienną. Przy umiejętnym stosowaniu i znajomości kilku sztuczek można nawet zmodyfikować menu Subiekta dodając do niego własną opcję.

Aby skorzystać z powyższego rozwiązania zapoznaj się z warunkami użytkowania.