Punkt drugi założeń projektowych (które można znaleźć tutaj) głosi:
2. numer faktury definiowany szablonem,
Czas zająć się tą funkcjonalnością. Projektując bazę danych zdecydowałem, że zarówno numer faktury jak i jego szablon będą łańcuchami znaków. Spójrzmy na przykład: “1/08/2010” – numer pierwszej faktury w sierpniu 2010. Według reguł, które zaraz przedstawię, szablon takiego numeru wygląda następująco: “N/MM/RR”.
Struktura szablonu numeru faktury
Numer kolejny faktury, jak można wywnioskować ze wstępu, powinien zawierać:
– informację (zwaną dalej numerem) mówiącą, którą fakturą w danym miesiącu jest dana faktura,
– miesiąc wystawienia faktury,
– rok wystawienia faktury.
Każdemu z tych elementów przypisałem literę: ‘N’ oznacza numer, ‘M’ – miesiąc, ‘R’ – rok. Użytkownik powinien móc zdecydować, czy miesiąc (przykładowo) sierpień będzie oznaczany jako “8”, czy “08”, i czy rok 2010 to “2010”, czy “10”. Podobnie, to od widzimisię użytkownika powinno zależeć, czy pierwsza faktura danego miesiąca otrzyma numer “1”, “01”, czy może “001”. Wszystkie te kwestie rozwiązałem w prosty i, mam nadzieję, intuicyjny sposób:
– pojawienie się “M” w formacie oznacza, że (przykładowo) sierpień zostanie zapisany jako “8”,
– pojawienie się “MM” – jako “08”,
– “R” – rok 2010 zostanie zapisany jako “10”,
– “RR” – jako “2010”,
– “N” – pierwsza faktura miesiąca otrzyma numer “1”,
– “NN” – numer “01”,
– “NNN” – numer “001”, “NNNN” – “0001” i tak dalej.
Aby nie było bałaganu, numer, miesiąc i rok mogą (i muszą) wystąpić tylko raz – poprawany jest szablon “N/M/R”, a niepoprawne: “N/R”, “N/M/R/R”. Co więcej, symbole te muszą być oddzielone separatorami, na przykład, jak we wszystkich powyższych przykładach, slashem.
Klasę obsługującą numery faktur nazwałem InvoiceNumber. Działa ona w obie strony (tworzy numer kolejny faktury zgodny z zadanym szablonem na podstawie numeru, miesiąca i daty, a także odczytuje numer, miesiąc i datę na podstawie numeru kolejnego i jego szablonu) i wygląda tak:
using System.Collections.Generic; using System.Linq; namespace InvoiceInvoker.Logic { public class InvoiceNumber { public int Number { get; private set; } public int Month { get; private set; } public int Year { get; private set; } public string Format { get; private set; } public List<string> formatArray = new List<string>(); public InvoiceNumber(int number, int month, int year, string format = "N/MM/RR") { Number = number; Month = month; Year = year; Format = CheckFormat(format) ? format : "N/MM/RR"; // default format is N/MM/RR SetFormatArray(); } public InvoiceNumber(string invoiceNumber, string format = "N/MM/RR") { Format = CheckFormat(format) ? format : "N/MM/RR"; // default format is N/MM/RR SetFormatArray(); ReadFromString(invoiceNumber); } public static bool CheckFormat(string format) { // examples of proper formats: "N/M/R", "RR-MM-NN" // N - invoice number // M - month // R - year // format must contain letters: N, M, R if (format.Contains('N') == false) return false; if (format.Contains('M') == false) return false; if (format.Contains('R') == false) return false; // format cannot contain digits if (format.Any(c => char.IsDigit(c))) return false; int firstIndexOfN = format.IndexOf('N'); int lastIndexOfN = format.LastIndexOf('N'); int firstIndexOfM = format.IndexOf('M'); int lastIndexOfM = format.LastIndexOf('M'); int firstIndexOfR = format.IndexOf('R'); int lastIndexOfR = format.LastIndexOf('R'); // letters N must form a sequence, e.g. "NNN/M/R" string sequence = format.Substring(firstIndexOfN, lastIndexOfN - firstIndexOfN + 1); if (sequence.Any(c => c != 'N')) return false; // letters M, R can only occur in the following combinations: // ...X... // ...XX... // e.g. formats "N/M/M/R", "N:M:MM:R" are not allowed if (lastIndexOfM - firstIndexOfM > 1) return false; // allows only one occurance of the following: "...N...", "...NN..." if (lastIndexOfR - firstIndexOfR > 1) return false; // letters N, M, R cannot occur next to each other if (firstIndexOfM - lastIndexOfN == 1) return false; // disqualifies "NM" sequence if (firstIndexOfN - lastIndexOfM == 1) return false; // disqualifies "MN" sequence if (firstIndexOfR - lastIndexOfM == 1) return false; // disqualifies "MR" sequence if (firstIndexOfM - lastIndexOfR == 1) return false; // disqualifies "RM" sequence if (firstIndexOfR - lastIndexOfN == 1) return false; // disqualifies "NR" sequence if (firstIndexOfN - lastIndexOfR == 1) return false; // disqualifies "RN" sequence return true; } private void SetFormatArray() { formatArray.Add(Format[0].ToString()); for (int i = 1; i != Format.Length; i++) { if (Format[i] == 'N' || Format[i] == 'M' || Format[i] == 'R') // current char is a symbol of number { if (Format[i] == Format[i - 1]) // current char is a part of number sequence formatArray[formatArray.Count - 1] += Format[i]; else // current char starts a number sequence formatArray.Add(Format[i].ToString()); } else // current char is a symbol of separator { if (Format[i - 1] == 'N' || Format[i - 1] == 'M' || Format[i - 1] == 'R') // current char starts a separator sequence formatArray.Add(Format[i].ToString()); else // current char is a part of separator sequence formatArray[formatArray.Count - 1] += Format[i]; } } } private void ReadFromString(string invoiceNumber) { string temp = invoiceNumber; for (int index = 0; index != formatArray.Count; index++) { if (formatArray[index].Contains('N')) // section under the index is the number section { string number = string.Empty; // e.g. if temp == "11/12/13" ... while (temp.Length > 0 && char.IsDigit(temp[0])) { number += temp[0]; temp = temp.Remove(0, 1); } // ... then number == "11" and temp == "/12/13" Number = int.Parse(number); } else if (formatArray[index].Contains('M')) // section under the index is the month section { string month = string.Empty; while (temp.Length > 0 && char.IsDigit(temp[0])) { month += temp[0]; temp = temp.Remove(0, 1); } Month = int.Parse(month); } else if (formatArray[index].Contains('R')) // section under the index is the year section { string year = string.Empty; while (temp.Length > 0 && char.IsDigit(temp[0])) { year += temp[0]; temp = temp.Remove(0, 1); } Year = int.Parse(year); if (formatArray[index] == "R") // e.g if year in string is "10" ... Year += 2000; // ... then the property should be 2010 } else // section under the index is a separator section { temp = temp.Remove(0, formatArray[index].Length); } } } public override string ToString() { string result = string.Empty; for (int index = 0; index != formatArray.Count; index++) { if (formatArray[index].Contains('N')) // section under the index is the number section { int amountOfZeros = formatArray[index].Length - Number.ToString().Length; // e.g. if formatArray[index] == "NNNNN" and Number == 1 ... for (int i = 0; i < amountOfZeros; i++) result += "0"; result += Number.ToString(); // ... then the number is converted into "00001" } else if (formatArray[index] == "M") // section under the index is the month section { result += Month.ToString(); } else if (formatArray[index] == "MM") // section under the index is the month section { if (Month < 10) // e.g. if Month == 1 ... result += "0"; result += Month.ToString(); // ... then it's converted into "01" } else if (formatArray[index] == "R") // section under the index is the year section { int year = Year - 2000; // e.g. if Year == 2001 ... if (year < 10) result += "0"; result += year.ToString(); // ... then it's converted into "01" } else if (formatArray[index] == "RR") // section under the index is the year section { result += Year.ToString(); } else // section under the index is a separator section { result += formatArray[index]; } } return result; } } }
Kolejną funkcjonalnością będzie najprawdopodobniej graficzna reprezentacja faktury w formacie PDF – ale o tym we wrześniu (patrz ostatni akapit poprzedniego wpisu).
1 thought on “Numer kolejny faktury”
Comments are closed.