Trik menyiasati kodifikasi nomor dokumen yang dinamis

March 31, 2010 at 9:33 am | Posted in Code Samples, Tutorials | 3 Comments
Tags:

Oleh: Jaimy Azle

Ada kasus yang cukup menggelitik yang pernah saya temui dalam proses pengembangan aplikasi Invoicing: perusahaan memiliki cukup banyak pelanggan/customer dan dalam menerbitkan dokumen invoice, perusahaan menerapkan bentuk format penomoran dokumen invoice yang berbeda-beda untuk tiap-tiap pelanggan yang ada.🙂

Studi Kasus

Adalah satu hal yang lumrah dalam penerbitan dokumen invoice perusahaan menerapkan kodifikasi penomoran untuk setiap dokumen invoice yang diterbitkan, sebagai contoh: 30912/INV/EXP/III/2010. Hal ini dilakukan untuk mempermudah manajemen dan pengaturan atas dokumen yang dibuat. Permasalahannya adalah bagaimana jika kodifikasi tersebut bersifat dinamis, dalam artian bergantung pada pelanggan-nya, dalam arti misalnya: untuk pelangan PT. DAUN BUAH menggunakan format dokumen 30912/EXP/MAR/2010, sementara untuk PT. SEGARA PERKASA berformat 30912/EXP/03/2010, dan untuk PT. ABADI NUSA menggunakan format 30912/EXP/INV/2010. Hal ini tentunya akan menjadi kasus yang cukup menggelitik untuk dipecahkan.

Pada kasus tersebut, saya memecahkannya dengan membuat rutin khusus untuk menangani kodifikasi yang dinamis tersebut dengan cara melakukan parsing atas format dokumen yang digunakan. Sebagai contoh, dokumen bernomor 30912/EXP/Mar/2010 terdiri atas elemen-elemen berikut: nomor dokumen (30912), bulan (Mar) dan tahun (2010) adapun karakter lainnya dapat dianggap sebagai karakter penyerta (/EXP/).

Secara keseluruhan, format kodifikasi penomoran dokumen pada umumnya melibatkan dua variabel utama, yaitu tanggal, dan nomor dokumen itu sendiri. Atas dua jenis variabel tersebut kemudian kita bisa menyusun elemen-elemen pembentuk atas format nomor dokumen yang akan dibuat, dalam hal ini misalnya: X untuk sebagai nomor dokumen, D untuk tanggal, M untuk bulam, dan Y untuk tahun. Untuk lebih mempermudah pengimplementasian, saya menggunakan aturan yang diterapkan pada bahasa C/C++ untuk memformat tanggal dengan beberapa penambahan fleksibilitas. Sebagai contoh, berikut ini adalah contoh nomor dokumen dan format yang saya implementasikan:

30912/EXP/Mar/2010 = [XXXXX]/EXP/[MMM]/[YYYY]
Elemen bulan dalam hal ini menggunakan metode penamaan pendek (Jan, Feb, Mar, dst), untuk hal tersebut kita menggunakan M sebanyak 3 kali.

00912/INV/III/10 = [XXXXX]/EXP/[M]/[YY]
Elemen bulan dalam hal ini menggunakan metode bilangan romawi, untuk kasus ini saya memanfaatkan elemen M (dikapitalisasi) satu perulangan. begitu pula dengan elemen tahun (Y), dalam hal ini penomoran yang diinginkan adalah menggunakan model dua digit tahun, karenanya kodifikasi disesuaikan dengan menggunakan dua elemen Y.

30912/INV/30/March/2010 = [XXXXX]/INV/[DD]/[MMMM]/[YYYY]
Dalam kasus ini, penomoran dokumen yang diinginkan adalah menggunakan elemen dua digit tanggal karenanya format atas dokumen ini menggunakan dua elemen D. Di sisi lain penomoran tersebut menggunakan nama bulan secara penuh, karenanya format elemen bulan tersebut disesuaikan dengan menggunakan 4 elemen M.

Implementasi

Pemecahan yang paling fleksibel atas kasus di atas adalah dengan memanfaatkan metode parsing atas format dokumen yang ada, parameter input dalam hal ini adalah format dokumen, nomor dokumen, serta tanggal. Proses parsing dilakukan dengan cara memisahkan antara elemen penenomoran serta karakter pembentuk, dalam hal ini untuk membedakan antara elemen dan karakter pembentuk dilakukan dengan mengapit elemen dengan dengan menggunakan karakter yang dianggap paling jarang digunakan, dalam hal ini adalah karakter “[” sebagai pembuka elemen dan karakter “]” sebagai penutup. Atas karakter-karakter yang tidak dianggap sebagai elemen langsung kita salin sebagai output, kemudian saat menemukan elemen yang dikenal maka program akan menghitung berapa jumlah karakter elemen yang digunakan serta jumlah kapitalisasinya kemudian menyisipkan dengan nilai yang sesuai atas elemen tersebut. Berikut adalah implementasi kode serta cara penggunaannya, adapun versi lain yang telah ditingkatkan fleksibilitasnya (namun ditulis dengan menggunakan Python) dapat dilihat di sini.


function FormatNumber(const AFormat: string;
  const AValue: integer;  
  const AYear, APeriod: word): string;
const
  APeriodList: array[1..12] of string = 
  ('I','II','III','IV','V','VI',
   'VII','VIII','IX','X','XI','XII');
var
  BufPos: Integer;
  Buffer: array[0..255] of Char;

  procedure AppendChars(P: PChar; Count: Integer);
  var
    N: Integer;
  begin
    N := SizeOf(Buffer) - BufPos;
    if N > Count then N := Count;
    if N  0 then Move(P[0], Buffer[BufPos], N);
    Inc(BufPos, N);
  end;

  procedure AppendString(const S: string);
  begin
    AppendChars(Pointer(S), Length(S));
  end;

  procedure AppendNumber(Number, Digits: Integer);
  const
    Format: array[0..3] of Char = '%.*d';
  var
    NumBuf: array[0..15] of Char;
  begin
    AppendChars(NumBuf, FormatBuf(NumBuf, SizeOf(NumBuf), Format,
      SizeOf(Format), [Digits, Number]));
  end;

  procedure AppendFormat(AFormat: PChar);
  var
    Starter, Token: Char;
    ACount, ACapital, ATotal: integer;

    procedure GetTotal;
    var
      P: PChar;
    begin
      P := AFormat;
      while AFormat^ = Starter do Inc(AFormat);
      ATotal := AFormat - P + 1;
    end;

  begin
    if (AFormat  nil) then
    begin
      ACount := 0;
      while AFormat^  #0 do
      begin
        Starter := AFormat^;
        AFormat := StrNextChar(AFormat);
        Token := Starter;
        if Token in ['a'..'z'] then Dec(Token, 32);
        case Token of
          '[': Inc(ACount);
          ']': Dec(ACount);
          'X':
            begin
              if (ACount > 0) then
              begin
                GetTotal;
                AppendNumber(AValue, ATotal)
              end else
                AppendChars(@Starter, 1);
            end;
          'M':
            begin
              if (ACount > 0) then
              begin
                GetTotal;
                if (Starter = Token) then
                  AppendString(APeriodList[APeriod])
                else
                begin
                  case ATotal of
                    1, 2: AppendNumber(APeriod, ATotal);
                    3: AppendString(UpperCase(ShortMonthNames[APeriod]));
                  else
                    AppendString(UpperCase(LongMonthNames[APeriod]));
                  end;
                end;
              end else
                AppendChars(@Starter, 1);
            end;
          'Y':
            begin
              if (ACount > 0) then
              begin
                GetTotal;
                if ATotal <= 2 then
                  AppendNumber(AYear mod 100, 2)
                else
                  AppendNumber(AYear, 4);
              end else
                AppendChars(@Starter, 1);            
            end;
          else
            AppendChars(@Starter, 1);
        end;
      end;
    end;
  end;
var
  AStrList: TStringList;
  AStr: string;
begin  
  BufPos := 0;
  if AFormat  '' then
  begin
    if (Pos(',',AFormat) > 0) then
    begin
      AStrList := TStringList.Create;
      try
        AStrList.CommaText := AFormat;
        AStr := IfThen(ASample, AStrList[1], AStrList[0]);
        AppendFormat(Pointer(AStr));
      finally
        AStrList.Free;
      end
    end else
      AppendFormat(Pointer(AFormat));
  end;
  SetString(Result, Buffer, BufPos);
end;

3 Comments »

RSS feed for comments on this post. TrackBack URI

  1. kalu untuk tiap periode reset untuk penomoran dokumen paling gampang gimana ya?

  2. Wah menarik nih kasusnya…ta simpan dah klo ada masalah kaya gini ditempatku..Terima kasih

  3. nice info gan… numpang copy & dipelajari yah …..


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: