Skip to content

15. Uygulama alıştırması - sürüm 4

Image

Burada, |Version 3| paragrafında açıklanan alıştırmayı ele alıyoruz ve şimdi bunu sınıflar ve arayüzler kullanarak işliyoruz. İki uygulama yazacağız:

Uygulama 1 şu şekilde olacaktır:

Image

[main] adlı ana komut dosyası, bir [dao] katmanını ve bir [métier] katmanını oluşturacaktır:

  • [dao] katmanının görevi, metin dosyalarında ve daha sonra bir veritabanında depolanan verileri yönetmek olacaktır;
  • [métier] katmanının görevi ise vergi hesaplamasını yapmak olacaktır;

Bu uygulamada kullanıcı tarafından herhangi bir işlem yapılmayacaktır: vergi mükelleflerinin verileri, adı [main] modülüne verilecek bir metin dosyasında bulunacaktır.

Uygulama 2’de ise vergi mükelleflerinin verilerini kullanıcı klavyeyle girecektir. Bu durumda mimari şu şekilde değişecektir:

Image

  • [dao] katmanı (Veri Erişim Nesnesi), harici verilere erişimden sorumludur
  • [métier] katmanı iş sorunlarıyla, bu durumda vergi hesaplamasıyla ilgilenir. Verilerle ilgilenmez. Veriler iki kaynaktan gelebilir:
    • kalıcı veriler için [dao] katmanı;
    • kullanıcı tarafından sağlanan veriler için [ui] katmanı.
  • [ui] katmanı (Kullanıcı Arayüzü) kullanıcı etkileşimlerini yönetir;
  • [main] ise tüm bu süreçleri koordine eder;

Bundan sonra, [dao], [métier] ve [ui] katmanlarının her biri bir sınıf kullanılarak uygulanacaktır. [métier] ve [dao] katmanları her iki uygulama için de aynı olacaktır. Bu nedenle, bunlar uygulama alıştırmasının aynı sürümünde birleştirilmiştir.

15.1. Sürüm 4 – uygulama 1

Sürüm 4, bir metin dosyasına yerleştirilmiş vergi mükellefleri listesinin vergisini hesaplar. Aşağıdaki mimariye sahiptir:

Image

15.1.1. Varlıklar

Image

Varlıklar, veri sınıflarıdır. Görevleri, verileri kapsüllemek ve bu verilerin geçerliliğini doğrulamaya olanak tanıyan getter/setter yöntemleri sunmaktır. Varlıklar, katmanlar arasında aktarılır. Aynı varlık, [ui] katmanından [dao] katmanına gidebilir ve bunun tersi de geçerlidir.

15.1.1.1. [ImpôtsError] sınıfı

Özel bir istisna sınıfı kullanacağız:


# -------------------------------
# istisna sınıfı
from MyException import MyException


class ImpôtsError(MyException):
    pass

[métier] ve [dao] katmanları bir sorunla karşılaştıklarında bu istisnayı tetikleyecektir. Bu sınıf, [MyException] sınıfından türetilmiştir. Dolayısıyla şu şekilde kullanılır: [raise ImpôtsError(code_erreur, msg_erreur)].

15.1.1.2. [AdminData] sınıfı

[AdminData] sınıfı, vergi hesaplamasında kullanılan sabitleri içerir:


from BaseEntity import BaseEntity


# vergi idaresi verileri
class AdminData(BaseEntity):
    # sınıf durumundan hariç tutulan anahtarlar
    excluded_keys = []

    # izin verilen anahtarlar
    @staticmethod
    def get_allowed_keys() -> list:
        return [
            "limites",
            "coeffr",
            "coeffn",
            "plafond_qf_demi_part",
            "plafond_revenus_celibataire_pour_reduction",
            "plafond_revenus_couple_pour_reduction",
            "valeur_reduc_demi_part",
            "plafond_decote_celibataire",
            "plafond_decote_couple",
            "plafond_impot_couple_pour_decote",
            "plafond_impot_celibataire_pour_decote",
            "abattement_dixpourcent_max",
            "abattement_dixpourcent_min"
        ]
    1. satır: [AdminData] sınıfı, |BaseEntity| paragrafında açıklanan [BaseEntity] sınıfını genişletir. [BaseEntity] sınıfını genişleten sınıfların şunları tanımlaması gerektiğini hatırlayalım:
    2. nesne sözlüğe dönüştürüldüğünde hariç tutulan özellikleri listeleyen bir [excluded_keys] sınıf özniteliği (7. satır);
    3. nesne bir sözlükle başlatıldığında kabul edilen özelliklerin listesini döndüren [get_allowed_keys] statik yöntemi (satır 10-26);

Bir [AdminData] nesnesini başlatmak için kullanılan verilerin geçerliliğini kontrol etmek amacıyla setter yöntemleri kullanılmamıştır. Zira bu nesne benzersizdir ve yapılandırma yoluyla tanımlandığından hatalı olma ihtimali yoktur.

15.1.1.3. [TaxPayer] sınıfı

[TaxPayer] sınıfı bir vergi mükellefini modelleyecektir:


# ithalatlar
from BaseEntity import BaseEntity
from ImpôtsError import ImpôtsError


# bir vergi mükellefi
class TaxPayer(BaseEntity):
    # bir vergi mükellefini modelleştirir
    # id: kimlik numarası
    # evli: evet / hayır
    # çocuklar: çocuk sayısı
    # maaş: yıllık maaşı
    # vergi: ödenecek vergi tutarı
    # ek vergi: ödenecek ek vergi
    # indirim: ödenecek vergiden yapılan indirim
    # indirim: ödenecek vergiden yapılan indirim
    # oran: vergi mükellefinin vergi oranı

    # sınıf durumundan hariç tutulan anahtarlar
    excluded_keys = []

    # izin verilen anahtarlar
    @staticmethod
    def get_allowed_keys() -> list:
        return ['id', 'marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']

    # özellikler
    @property
    def marié(self) -> str:
        return self.__marié

    @property
    def enfants(self) -> int:
        return self.__enfants

    @property
    def salaire(self) -> int:
        return self.__salaire

    @property
    def impôt(self) -> int:
        return self.__impôt

    @property
    def surcôte(self) -> int:
        return self.__surcôte

    @property
    def décôte(self) -> int:
        return self.__décôte

    @property
    def réduction(self) -> int:
        return self.__réduction

    @property
    def taux(self) -> float:
        return self.__taux

    # ayarlama yöntemleri
    @marié.setter
    def marié(self, marié: str):
        ok = isinstance(marié, str)
        if ok:
            marié = marié.strip().lower()
            ok = marié == "oui" or marié == "non"
        if ok:
            self.__marié = marié
        else:
            raise ImpôtsError(31, f"l'attribut marié [{marié}] doit avoir l'une des valeurs oui / non")

    @enfants.setter
    def enfants(self, enfants):
        # çocuk sayısı >=0 olan bir tamsayı olmalıdır
        try:
            enfants = int(enfants)
            erreur = enfants < 0
        except:
            erreur = True
        if not erreur:
            self.__enfants = enfants
        else:
            raise ImpôtsError(32, f"L'attribut enfants [{enfants}] doit être un entier >=0")

    @salaire.setter
    def salaire(self, salaire):
        # maaş, 0'dan büyük bir tamsayı olmalıdır
        try:
            salaire = int(salaire)
            erreur = salaire < 0
        except:
            erreur = True
        if not erreur:
            self.__salaire = salaire
        else:
            raise ImpôtsError(33, f"L'attribut salaire [{salaire}] doit être un entier >=0")

    @impôt.setter
    def impôt(self, impôt):
        # vergi, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
        try:
            impôt = int(impôt)
            erreur = impôt < 0
        except:
            erreur = True
        if not erreur:
            self.__impôt = impôt
        else:
            raise ImpôtsError(34, f"L'attribut impôt [{impôt}] doit être un nombre >=0")

    @décôte.setter
    def décôte(self, décôte):
        # indirim >=0 olan bir tamsayı olmalıdır
        try:
            décôte = int(décôte)
            erreur = décôte < 0
        except:
            erreur = True
        if not erreur:
            self.__décôte = décôte
        else:
            raise ImpôtsError(35, f"L'attribut décôte [{décôte}] doit être un nombre >=0")

    @surcôte.setter
    def surcôte(self, surcôte):
        # fazla ücret, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
        try:
            surcôte = int(surcôte)
            erreur = surcôte < 0
        except:
            erreur = True
        if not erreur:
            self.__surcôte = surcôte
        else:
            raise ImpôtsError(36, f"L'attribut surcôte [{surcôte}] doit être un nombre >=0")

    @réduction.setter
    def réduction(self, réduction):
        # fazla ücret, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
        try:
            réduction = int(réduction)
            erreur = réduction < 0
        except:
            erreur = True
        if not erreur:
            self.__réduction = réduction
        else:
            raise ImpôtsError(37, f"L'attribut réduction [{réduction}] doit être un nombre >=0")

    @taux.setter
    def taux(self, taux):
        # oran, >=0 olan bir gerçek sayı olmalıdır
        try:
            taux = float(taux)
            erreur = taux < 0
        except:
            erreur = True
        if not erreur:
            self.__taux = taux
        else:
            raise ImpôtsError(38, f"L'attribut taux [{taux}] doit être un nombre >=0")

Notlar:

  • [TaxPayer] sınıfı bir vergi mükellefini kapsar;
    1. satır: [TaxPayer] sınıfı, [BaseEntity] sınıfından türetilmiştir. Dolayısıyla, [id] tanımlayıcısına sahiptir;
    1. satır: [AdminData] nesnesinin durumundan hiçbir özellik hariç tutulmamıştır;
  • 22-25. satırlar: Sınıfın özellikleri. Bunlar 9-17. satırlarda açıklanmıştır;
  • satır 27-58: sınıf özniteliklerinin getter'ları;
  • satır 60-161: sınıfın özniteliklerinin setter'ları. Verileri kapsülleyen bir sınıfın, basit bir sözlüğe kıyasla avantajı, sınıfın setter'ları sayesinde özelliklerinin geçerliliğini doğrulayabilmesidir;

15.1.2. [dao] katmanı

Image

Katmanların uygulamalarını [services] klasöründe bir araya getireceğiz. Bu sınıflar, [interfaces] klasöründe tanımlanan arayüzleri uygulayacaklardır.

Image

15.1.2.1. [InterfaceImpôtsDao] arayüzü

[dao] katmanı, aşağıdaki [InterfaceImpôtsDao] arayüzünü (InterfaceImpôtsDao.py dosyası) uygulayacaktır:


# ithalatlar
from abc import ABC, abstractmethod


# IImpôtsDao arayüzü
from AdminData import AdminData


class InterfaceImpôtsDao(ABC):
    # vergi dilimleri listesi
    @abstractmethod
    def get_admindata(self) -> AdminData:
        pass

    # vergi mükellefi verileri listesi
    @abstractmethod
    def get_taxpayers_data(self) -> dict:
        pass

    # vergi hesaplama sonuçlarının kaydı
    @abstractmethod
    def write_taxpayers_results(self, taxpayers_results: list):
        pass

Arayüz üç yöntem tanımlar:

  • [get_admindata]: vergi dilimlerinin dizisini alan yöntemdir. Bu verilerin nasıl elde edileceğine dair herhangi bir bilgi verilmediğine dikkat edilmelidir. İlerleyen bölümlerde, bu veriler önce bir metin dosyasında, ardından bir veritabanında bulunacaktır. Verilerin depolanma biçimine uyum sağlamak, arayüzü uygulayan sınıfların sorumluluğundadır. Dolayısıyla, vergi dilimlerini bir metin dosyasından almak için bir sınıf ve bir veritabanından almak için başka bir sınıf olacaktır. Her ikisi de [get_admindata] yöntemini uygulayacaktır;
  • [get_taxpayers_data]: vergi mükelleflerinin verilerini alan yöntemdir. Burada da verilerin nerede bulunacağı belirtilmemiştir. Yalnızca verilerin bir metin dosyasında bulunduğu durumu ele alacağız;
  • [write_taxpayers_results]: vergi hesaplama sonuçlarını kalıcı hale getirecek yöntemdir. Sonuçların nereye kaydedileceğini belirtmiyoruz. Yalnızca sonuçların bir metin dosyasına kaydedildiği durumu ele alacağız. [taxpayers_results] parametresi, kalıcı hale getirilecek sonuçların listesi olacaktır;

15.1.2.2. [AbstractImpôtsDao] sınıfı

[dao] katmanı iki sınıf tarafından uygulanacaktır:

  • biri metin dosyalarından verileri (vergi mükellefleri, sonuçlar, vergi dilimleri) alacaktır;
  • diğeri ise verileri (vergi mükellefleri, sonuçlar) metin dosyalarından ve vergi dilimlerini bir veritabanından alacaktır;

İki sınıf arasında tek fark, vergi dilimlerinin yönetimi olacaktır. Vergi mükellefi verileri ve vergi hesaplama sonuçları ise aynı şekilde yönetilecektir. Bu nedenle, bunları [AbstractImpôtsDao] adlı bir üst sınıfta yöneteceğiz. Vergi dilimlerinin yönetimine ilişkin özel durum ise iki alt sınıfta ele alınacaktır:

  • [ImpôtsDaoWithAdminDataInJsonFile] sınıfı, jSON formatındaki bir metin dosyasından vergi dilimlerini alacaktır;
  • [ImpôtsDaoWithAdminDataInDatabase] sınıfı, vergi dilimlerini bir veritabanından alacaktır;

Üst sınıf [AbstractImpôtsDao] şu şekilde olacaktır:


# ithalatlar
import codecs
import json
from abc import abstractmethod

from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDao import InterfaceImpôtsDao
from TaxPayer import TaxPayer


# [dao] katmanı için temel sınıf
class AbstractImpôtsDao(InterfaceImpôtsDao):
    # Vergi mükellefleri ve vergileri metin dosyalarında yer alacaktır
    # oluşturucu
    def __init__(self, config: dict):
        # config[taxpayersFilename] : vergi mükelleflerinin bulunduğu metin dosyasının adı
        # yapılandırma[resultsFilename] : sonuçların jSON dosya adı
        # config[errorsFilename]: hata dosyasının adı

        # ayarlar kaydedilir
        self.taxpayers_filename = config.get("taxpayersFilename")
        self.taxpayers_results_filename = config.get("resultsFilename")
        self.errors_filename = config.get("errorsFilename")

    # ------------------
    # IImpôtsDao arayüzü
    # ------------------

    # vergi mükellefleri verileri listesi
    def get_taxpayers_data(self) -> dict:
        

    # vergi mükelleflerinin vergilerinin kaydedilmesi
    def write_taxpayers_results(self, taxpayers: list):
        

    # vergi dilimlerinin okunması
    @abstractmethod
    def get_admindata(self) -> AdminData:
        pass
    1. satır: [AbstractImpôtsDao] sınıfı, [InterfaceImpôtsDao] arayüzünü uygular. Dolayısıyla bu arayüzün üç yöntemi de burada bulunur:
    2. [get_taxpayers_data]: satır 31;
    3. [write_taxpayers_results]: satır 35;
    4. [get_admindata]: 40. satır. Bu yöntem, [AbstractImpôtsDao] sınıfı tarafından uygulanmayacağı için soyut olarak tanımlanmıştır (39. satır);
  • satır 16: oluşturucu, aşağıdaki bilgileri içeren bir [config] sözlüğü alır:

    • [taxpayersFilename]: vergi mükelleflerinin verilerini içeren metin dosyasının adı;
    • [resultsFilename]: sonuçların kaydedileceği metin dosyasının adı;
    • [errorsFilename]: [taxpayersFilename] dosyasının işlenmesi sırasında karşılaşılan hataları listeleyen metin dosyasının adı;

[get_taxpayers_data] yöntemi şu şekildedir:


    # vergi mükellefi verileri listesi
    def get_taxpayers_data(self) -> dict:
        # başlatma işlemleri
        taxpayers_data = []
        datafile = None
        erreurs = []
        try:
            # veri dosyasının açılması
            datafile = open(self.taxpayers_filename, "r")
            # dosyanın geçerli satırını işleme
            ligne = datafile.readline()
            # satır numarası
            numligne = 0
            while ligne != '':
                # bir satır ekleniyor
                numligne += 1
                # boşluklar kaldırılıyor
                ligne = ligne.strip()
                # boş satırları ve yorumları atlıyoruz
                if ligne != "" and ligne[0] != "#":
                    try:
                        # vergi mükellefi satırını oluşturan id, evli, çocuk sayısı, maaş olmak üzere 4 alanı alınır
                        (id, marié, enfants, salaire) = ligne.split(",")
                        # yeni bir TaxPayer oluşturulur
                        taxpayers_data.append(
                            TaxPayer().fromdict({'id': id, 'marié': marié, 'enfants': enfants, 'salaire': salaire}))
                    except BaseException as erreur:
                        # hata kaydedilir
                        erreurs.append(f"Ligne {numligne}, {erreur}")
                # yeni bir vergi mükellefi satırı okunur
                ligne = datafile.readline()
            # varsa hataları kaydedilir
            if erreurs:
                text = f"Analyse du fichier {self.taxpayers_filename}\n\n" + "\n".join(erreurs)
                with codecs.open(self.errors_filename, "w", "utf-8") as fd:
                    fd.write(text)
            # sonuç döndürülür
            return {"taxpayers": taxpayers_data, "erreurs": erreurs}
        except BaseException as erreur:
            # bir istisna oluşturulur ImpôtsError
            raise ImpôtsError(11, f"{erreur}")
        finally:
            # dosya kapatılır
            if datafile:
                datafile.close()
    1. satır: vergi mükelleflerine ait veriler (evli olup olmadığı, çocuk sayısı, maaş) [TaxPayer] türünde bir nesne listesine yerleştirilecektir;
  • 8-9. satırlar: vergi mükelleflerine ait metin dosyası okuma modunda açılır. Dosyanın içeriği şu şekildedir:
# geçerli veriler: kimlik numarası, evli, çocuk sayısı, maaş
1,oui,2,55555
2,oui,2,50000
3,oui,3,50000
4,non,2,100000
5,non,3,100000
6,oui,3,100000
7,oui,5,100000
8,non,0,100000
9,oui,2,30000
10,non,0,200000
11,oui,3,200000
# boş satırlar olabilir

# hatalı satırlar oluşturulur
# yeterli değer yok
11,12
# fazla değer var
12,oui,3,200000, x, y
# hatalı değerler
x,x,x,x

Önceki sürümlerle karşılaştırıldığında:

  • [taxpayersFilename] dosyasının her satırı, vergi mükellefinin kimliği olan basit bir numara ile başlar;
  • yorumlar ve boş satırlar izin verilir;
  • hataları işleyeceğiz. Dolayısıyla 17, 19 ve 21. satırlar hatalı olarak işaretlenmelidir. Hatalar ayrı bir dosyaya kaydedilir;

Kodun incelemesine devam edelim:

    1. satır: metin dosyasındaki veriler [taxPayersData] listesine aktarılır;
  • 14-31. satırlar: vergi mükellefleri dosyası satır satır okunur;
    1. satır: Boş bir satır okunduğunda (hiçbir şey – hatta \r\n satır sonu işareti bile yok) dosyanın sonuna ulaşılır;
    1. satır: Boş satırlar ve yorumlar göz ardı edilir. Bir satır, metnin önündeki ve arkasındaki boşluklar temizlendikten sonra ilk karakteri # ise yorum olarak kabul edilir;
  • satır 24: Geçerli bir satır, virgülle ayrılmış dört alandan oluşur. Bu alanlar alınır. Atanan verilerin sayısı tam olarak dört değilse, dört elemanlı bir tuple’a veri atama işlemi başarısız olur;
  • satır 25: Alınan dört alandan biri ([id, marié, enfants, salaire]) geçersizse, [BaseEntity.fromdict] yöntemi [MyException] türünde bir istisna atar;
  • 25-26. satırlar: Bir [TaxPayer] nesnesi, vergi mükelleflerinin [taxpayers_data] listesine eklenir;
  • 27-29. satırlar: Olası hatalar [erreurs] listesinde toplanır. Bu liste 6. satırda oluşturulmuştur;
  • satır 33-36: Karşılaşılan hataların listesi, [errorsFilename] metin dosyasına kaydedilir. Hatalar iki türdür:
    • bir satırda beklenen doğru alan sayısı bulunmuyordu;
    • satırdaki bilgiler hatalıydı ve [TaxPayer] nesnesinin oluşturulmasında başarısızlık yaşandı;
  • satır 39-41: tüm hatalar (BaseException) yakalanır ve bir [ImpôtsError] türüne kapsüllenerek üst katmana bildirilir;
  • 42-45. satırlar: Başarılı ya da başarısız olsun, vergi mükelleflerinin metin dosyası açılmışsa kapatılır;

[write_taxpayers_results] yöntemi, aşağıdaki biçimde bir jSON dosyası oluşturmalıdır:


[
  {
    "id": 1,
    "marié": "oui",
    "enfants": 2,
    "salaire": 55555,
    "impôt": 2814,
    "surcôte": 0,
    "taux": 0.14,
    "décôte": 0,
    "réduction": 0
  },
  {
    "id": 2,
    "marié": "oui",
    "enfants": 2,
    "salaire": 50000,
    "impôt": 1384,
    "surcôte": 0,
    "taux": 0.14,
    "décôte": 384,
    "réduction": 347
  },
  {
    "id": 3,
    "marié": "oui",
    "enfants": 3,
    "salaire": 50000,
    "impôt": 0,
    "surcôte": 0,
    "taux": 0.14,
    "décôte": 720,
    "réduction": 0
  },

]

[write_taxpayers_results] yöntemi şu şekildedir:


    # mükelleflerin vergilerinin yazılması
    def write_taxpayers_results(self, taxpayers: list):
        # sonuçların bir dosyaya kaydedilmesi jSON
        # vergi mükellefleri: TaxPayer türündeki nesnelerin listesi
        # (kimlik, evli, çocuk sayısı, maaş, vergi, ek vergi, indirim, indirim oranı)
        # [taxpayers] listesi, [self.taxpayers_results_filename] metin dosyasına kaydedilir
        file = None
        try:
            # sonuç dosyasının açılması
            file = codecs.open(self.taxpayers_results_filename, "w", "utf8")
            # jSON formatında serileştirilecek listenin oluşturulması
            mapping = map(lambda taxpayer: taxpayer.asdict(), taxpayers)
            # serileştirme jSON
            json.dump(list(mapping), file, ensure_ascii=False)
        except BaseException as erreur:
            # hatayı başka bir tür altında yeniden tetikleme
            raise ImpôtsError(12, f"{erreur}")
        finally:
            # Dosya açıksa kapatılır
            if file:
                file.close()
    1. satır: Yöntem, [taxpayers] vergi mükellefleri listesini alır ve bunu jSON biçiminde [self.taxpayers_results_filename] metin dosyasına kaydetmelidir;
    1. satır: Sonuçların UTF-8 dosyasının oluşturulması;
    1. satır: Burada, sözdizimi [map (fonction, liste1)] olan [map] işlevini tanıtıyoruz. [fonction] fonksiyonu, [liste1]'in her bir elemanına uygulanır ve [liste2] listesine eklenecek yeni bir eleman üretir. Son olarak, her i için:

liste2[i]=fonction(liste1[i])

Burada, [liste1], [taxPayers] listesidir; bu da [TaxPayer] türündeki nesnelerden oluşan bir listedir. [fonction] işlevi burada, [lambda] adlı bir işlev biçiminde ifade edilmektedir; bu işlev, [taxpayers] listesindeki bir [taxpayer] elemanı üzerinde yapılan dönüşümü ifade eder: her bir [taxpayer] elemanı, kendi [taxpayer.asdict()] sözlüğüyle değiştirilir. Sonuç olarak, elde edilen [liste2] listesi, [taxpayers] listesindeki elemanların sözlüklerinin listesidir;

    1. satır: [map] işlevi tarafından döndürülen sonuç, [liste2] listesi değil, [map] türünde bir nesnedir. [liste2]'i elde etmek için [list(mapping)] ifadesini (satır 14) kullanmak gerekir;
    1. satır: [liste2] listesi, jSON biçiminde [self.taxpayers_results_filename] dosyasına kaydedilir;
  • satır 15-17: Her türlü istisna yakalanır ve yeniden çalıştırılmadan önce (satır 17) [ImpôtsError] türünde bir hataya kapsüllenir;
  • satır 19-21: Başarılı ya da başarısız olsun, sonuç dosyası açılmışsa her durumda kapatılır;

15.1.2.3. [ImpôtsDaoWithAdminDataInJsonFile] Sınıfı

[ImpôtsDaoWithAdminDataInJsonFile] sınıfı, [AbstractImpôtsDao] sınıfından türetilecek ve üst sınıfının uygulamadığı [getAdminData] yöntemini uygulayacaktır. Vergi idaresine ait verileri jSON dosyasından alacaktır:


{
    "limites": [9964, 27519, 73779, 156244, 0],
    "coeffr": [0, 0.14, 0.3, 0.41, 0.45],
    "coeffn": [0, 1394.96, 5798, 13913.69, 20163.45],
    "plafond_qf_demi_part": 1551,
    "plafond_revenus_celibataire_pour_reduction": 21037,
    "plafond_revenus_couple_pour_reduction": 42074,
    "valeur_reduc_demi_part": 3797,
    "plafond_decote_celibataire": 1196,
    "plafond_decote_couple": 1970,
    "plafond_impot_couple_pour_decote": 2627,
    "plafond_impot_celibataire_pour_decote": 1595,
    "abattement_dixpourcent_max": 12502,
    "abattement_dixpourcent_min": 437
}

[ImpôtsDaoWithAdminDataInJsonFile] sınıfı şu şekildedir:


# ithalatlar
import codecs
import json

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError


# [dao] katmanının bir uygulaması; burada vergi idaresine ait veriler bir dosyada bulunur jSON
class ImpôtsDaoWithAdminDataInJsonFile(AbstractImpôtsDao):
    # oluşturucu
    def __init__(self, config: dict):
        # config[admindataFilename]: vergi idaresi verilerini içeren jSON dosyasının adı
        # config[taxpayersFilename] : vergi mükelleflerine ait metin dosyasının adı
        # config[resultsFilename] : sonuçların bulunduğu jSON dosyasının adı
        # config[errorsFilename]: hata dosyasının adı

        # Parent sınıfının başlatılması
        AbstractImpôtsDao.__init__(self, config)
        # vergi idaresi verilerinin okunması
        file = None
        try:
            # vergi verilerini içeren jSON dosyasının okuma modunda açılması
            file = codecs.open(config["admindataFilename"], "r", "utf8")
            # jSON dosyasının içeriğinin bir [AdminData] nesnesine aktarılması
            self.admindata = AdminData().fromdict(json.load(file))
        except BaseException as erreur:
            # hata, [ImpôtsError] türü olarak yeniden gönderilir
            raise ImpôtsError(21, f"{erreur}")
        finally:
            # Dosya açıksa kapatılır
            if file:
                file.close()

    # -------------
    # arayüz
    # -------------

    # vergi idaresinden verilerin alınması
    # yöntem, bir [AdminData] nesnesi döndürür
    def get_admindata(self) -> AdminData:
        return self.admindata
    1. satır: [ImpôtsDaoWithAdminDataInJsonFile] sınıfı, [AbstractImpôtsDao] sınıfından miras alır. Bu nedenle [InterfaceImpôtsDao] arayüzünü uygular;
    1. satır: Oluşturucu, 14-17. satırlardaki bilgileri içeren bir sözlük parametresini alır;
    1. satır: üst sınıf başlatılır;
    1. satır: Vergi idaresi verilerini içeren jSON dosyası açılır;
    1. satır: Vergi idaresi verilerini içeren UTF-8 dosyası açılır;
    1. satır: Dosyanın içeriği okunur ve [AdminData] türündeki [self.admindata] nesnesine yerleştirilir. jSON dosyasının anahtarları, bir [AdminData] nesnesi için kabul edilen özelliklere uymalıdır; aksi takdirde [fromdict] yöntemi bir istisna oluşturur;
  • 28-30. satırlar: istisna yönetimi. Oluşabilecek istisnalar, yeniden atılmadan önce [ImpôtsError] türünde kapsüllenir;
  • satır 32-34: dosya açıksa kapatılır;
  • satır 42-43: [InterfaceImpôtsDao] arayüzünün [get_admindata] yönteminin uygulanması;

15.1.3. [métier] katmanı

Image

15.1.3.1. [InterfaceImpôtsMétier] arayüzü

[métier] katmanının arayüzü şu şekilde olacaktır:


# içe aktarmalar
from abc import ABC, abstractmethod

from AdminData import AdminData
from TaxPayer import TaxPayer


# IImpôtsMétier arayüzü
class InterfaceImpôtsMétier(ABC):
    # 1 vergi mükellefi için vergi hesaplaması
    @abstractmethod
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData):
        pass
  • [InterfaceImpôtsMétier] arayüzü tek bir yöntem tanımlar:
    1. satır: [calculate_tax] yöntemi, tek bir vergi mükellefi olan [taxpayer]'in vergisini hesaplamaya olanak tanır. [admindata], vergi idaresinin verilerini kapsayan [AdminData] nesnesidir;
    1. satır: [calculate_tax] yöntemi herhangi bir sonuç döndürmez. Elde edilen veriler (vergi, ek vergi, indirim, muafiyet, oran) [taxpayer] parametresine dahil edilir: yöntem çağrılmadan önce bu öznitelikler boştur, çağrıldıktan sonra ise başlatılır;

15.1.3.2. [ImpôtsMétier] sınıfı

[ImpôtsMétier] sınıfı, [InterfaceImpôtsMétier] arayüzünü aşağıdaki şekilde uygular:

Image

Sınıfın yöntemleri, |[impots.v02.modules.impôts_module_02] modülü| başlıklı bölümdeki [impôts_module_02] modülünden alınmıştır. Yöntemlerin parametre sayısı sadece ikiyle sınırlandırılmıştır:

  • taxpayer(id, evli, çocuk sayısı, maaş, vergi, indirim, ek vergi, indirim, oran): bir vergi mükellefini ve vergisini temsil eden nesne;
  • admindata: vergi idaresinin verilerini kapsayan nesne;

Yapılan bu değişiklikleri bir yöntem üzerinde gösteriyoruz;


    # vergi hesaplaması - 1. aşama
    # ----------------------------------------
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData):
        # vergi mükellefi (kimlik numarası, evli olup olmadığı, çocuk sayısı, maaş, vergi, indirim, ek vergi, indirim, vergi oranı)
        # idari veriler: vergi idaresi verileri

        # çocuklu vergi hesaplaması
        self.calculate_tax_2(taxpayer, admindata)
        # sonuçlar vergi mükellefi tablosunda yer almaktadır
        taux1 = taxpayer.taux
        surcôte1 = taxpayer.surcôte
        impot1 = taxpayer.impôt
        # çocuksuz vergi hesaplaması
        if taxpayer.enfants != 0:
            # çocuksuz aynı vergi mükellefi için vergi hesaplaması
            taxpayer2 = TaxPayer().fromdict(
                {'id': 0, 'marié': taxpayer.marié, 'enfants': 0, 'salaire': taxpayer.salaire})
            self.calculate_tax_2(taxpayer2, admindata)
            # sonuçlar taxpayer2'de
            taux2 = taxpayer2.taux
            surcôte2 = taxpayer2.surcôte
            impot2 = taxpayer2.impôt
            # aile katsayısı üst sınırının uygulanması
            if taxpayer.enfants < 3:
                # PLAFOND_QF_DEMI_PART avro, ilk 2 çocuk için
                impot2 = impot2 - taxpayer.enfants * admindata.plafond_qf_demi_part
            else:
                # PLAFOND_QF_DEMI_PART avro ilk 2 çocuk için, sonraki çocuklar için iki katı
                impot2 = impot2 - 2 * admindata.plafond_qf_demi_part - (taxpayer.enfants - 2) \
                         * 2 * admindata.plafond_qf_demi_part
        else:
            # Vergi mükellefinin çocuğu yoksa, o zaman vergi2=vergi1
            impot2 = impot1

        # ilgili vergi oranı ve ek vergiyle birlikte en yüksek vergi tutarı alınır
        (impot, surcôte, taux) = (impot1, surcôte1, taux1) if impot1 >= impot2 else (
            impot2, impot2 - impot1 + surcôte2, taux2)

        # ara sonuçlar
        taxpayer.impôt = impot
        taxpayer.surcôte = surcôte
        taxpayer.taux = taux
        # Olası bir indirimin hesaplanması
        self.get_décôte(taxpayer, admindata)
        taxpayer.impôt -= taxpayer.décôte
        # olası vergi indiriminin hesaplanması
        self.get_réduction(taxpayer, admindata)
        taxpayer.impôt -= taxpayer.réduction
        # sonuç
        taxpayer.impôt = math.floor(taxpayer.impôt)
    1. satır: [calculate_tax] yöntemi, [InterfaceImpôtsMétier] arayüzünün tek yöntemidir. Bu yöntem iki parametre kabul eder:
    2. [tapPayer]: Vergisi hesaplanan vergi mükellefi;
    3. [admindata]: vergi idaresinin verilerini kapsayan nesne;
    4. hesaplama sonuçları [taxpayer] parametresinde saklanır (satır 40-50). Dolayısıyla, bu nesnenin içeriği yöntem çağrılmadan önce ve sonra aynı değildir;

15.1.4. [dao] ve [métier] katmanlarının testleri

Image

  • [TestDaoMétier], [dao] ve [métier] katmanlarının test sınıfı olan UnitTest'tir;
  • [config], testlerin yapılandırma dosyasıdır;

[config] yapılandırması şu şekildedir:


def configure():
    import os

    # 1. adım ------
    # Python yolunun yapılandırılması

    # bu dosyanın bulunduğu klasör
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # root_dir
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"

    # uygulamanın mutlak bağımlılıkları
    absolute_dependencies = [
        f"{script_dir}/../entities",
        f"{script_dir}/../interfaces",
        f"{script_dir}/../services",
        f"{root_dir}/02/entities",
    ]

    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama yapılandırması
    config = {
        # uygulama dosyalarının mutlak yolları
        "admindataFilename"f"{script_dir}/../data/input/admindata.json"
    }

    # uygulama katmanlarının örneklenmesi
    from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
    from ImpôtsMétier import ImpôtsMétier

    dao = ImpôtsDaoWithAdminDataInJsonFile(config)
    métier = ImpôtsMétier()

    # katman örneklerini yapılandırmaya ekliyoruz
    config["dao"] = dao
    config["métier"] = métier

    # yapılandırma dosyası oluşturulur
    return config
  • 4-23. satırlar: Testlerin Python Yolu yapılandırılır;
  • 32-41. satırlar: [dao] ve [métier] katmanları oluşturulur. Referansları [config] sözlüğüne eklenir;
    1. satır: bu sözlük döndürülür;

[TestDaoMétier] test sınıfı şu şekildedir:


import unittest


def get_config() -> dict:
    # uygulama yapılandırması
    import config
    # yapılandırma geri yüklenir
    return config.configure()


class TestDaoMétier(unittest.TestCase):

    # her yöntemden önce yürütülür test_
    def setUp(self) -> None:
        # test yapılandırmasını alıyoruz
        config = get_config()
        # bazı bilgileri kaydediyoruz
        self.métier = config['métier']
        self.admindata = config['dao'].get_admindata()

    def test_1(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk': 2, 'maaş': 55555,
        # 'vergi': 2814, 'ek vergi': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0,14}
        taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # doğrulama
        self.assertAlmostEqual(taxpayer.impôt, 2815, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    def test_2(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk': 2, 'maaş': 50000,
        # 'vergi': 1384, 'ek ücret': 0, 'indirim': 384, 'azaltma': 347, 'oran': 0,14}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 2, 'salaire': 50000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 1384, delta=1)
        self.assertAlmostEqual(taxpayer.décôte, 384, delta=1)
        self.assertAlmostEqual(taxpayer.réduction, 347, delta=1)
        self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    def test_3(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk sayısı': 3, 'maaş': 50000,
        # 'vergi': 0, 'ek ücret': 0, 'indirim': 720, 'azaltma': 0, 'oran': 0.14}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 50000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertEqual(taxpayer.impôt, 0)
        self.assertAlmostEqual(taxpayer.décôte, 720, delta=1)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    def test_4(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'hayır', 'çocuk': 2, 'maaş': 100000,
        # 'vergi': 19884, 'ek ücret': 4480, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
        taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 2, 'salaire': 100000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 19884, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
        self.assertAlmostEqual(taxpayer.surcôte, 4480, delta=1)

    def test_5(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'hayır', 'çocuk sayısı': 3, 'maaş': 100000,
        # 'vergi': 16782, 'ek ücret': 7176, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
        taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 3, 'salaire': 100000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 16782, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
        self.assertAlmostEqual(taxpayer.surcôte, 7176, delta=1)

    def test_6(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk sayısı': 3, 'maaş': 100000,
        # 'vergi': 9200, 'ek ücret': 2180, 'indirim': 0, 'azaltma': 0, 'oran': 0.3}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 100000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 9200, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.3, delta=0.01)
        self.assertAlmostEqual(taxpayer.surcôte, 2180, delta=1)

    def test_7(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk sayısı': 5, 'maaş': 100000,
        # 'vergi': 4230, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0.14}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 5, 'salaire': 100000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 4230, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    def test_8(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'hayır', 'çocuk': 0, 'maaş': 100000,
        # 'vergi': 22986, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0,41}
        taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 0, 'salaire': 100000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 22986, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    def test_9(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk sayısı': 2, 'maaş': 30000,
        # 'vergi': 0, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 2, 'salaire': 30000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertEqual(taxpayer.impôt, 0)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.0, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    def test_10(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'hayır', 'çocuk': 0, 'maaş': 200000,
        # 'vergi': 64210, 'ek ücret': 7498, 'indirim': 0, 'azaltma': 0, 'oran': 0,45}
        taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 0, 'salaire': 200000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 64210, 1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.45, delta=0.01)
        self.assertAlmostEqual(taxpayer.surcôte, 7498, delta=1)

    def test_11(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk sayısı': 3, 'maaş': 200000,
        # 'vergi': 42842, 'ek ücret': 17283, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 200000})
        self.métier.calculate_tax(taxpayer, self.admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 42842, 1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
        self.assertAlmostEqual(taxpayer.surcôte, 17283, delta=1)


if __name__ == '__main__':
    unittest.main()

Açıklamalar

    1. satır: Test sınıfı, [unittest.TestCase] sınıfını genişletir;
  • 13-19. satırlar: UnitTest testinde, [setUp] yöntemi, her bir [test_] yönteminden önce yürütülür;
    1. satır: Daha önce incelenen [config] komut dosyasından elde edilen yapılandırma alınır;
    1. satır: [métier] katmanına bir referans kaydedilir;
  • satır 19: [dao] katmanından, vergi idaresi verilerini içeren [AdminData] nesnesini istenir ve bu nesne saklanır;
  • 21-173. satırlar: Sonuçları 2019 vergi dairesinin resmi web sitesinde |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm| üzerinden doğrulanmış 11 test;
  • 21-33. satırlar: tüm testler aynı şablona göre oluşturulmuştur;
    1. satır: [TaxPayer] sınıfı içe aktarılır;
    1. satır: test edilen vergi mükellefi;
  • satır 25: beklenen sonuçlar;
  • satır 26: vergi mükellefinin [TaxPayer] nesnesinin oluşturulması;
  • satır 27: vergi hesaplaması. Sonuç, [taxpayer]'te yer almaktadır;
  • satır 29-33: elde edilen sonuçların doğrulanması;
  • satır 29: vergi tutarı euro düzeyinde doğrulanır. Testler, bu belgedeki algoritma ile elde edilen sonuçların resmi rakamlardan en fazla 1 euro farklılık gösterebileceğini ortaya koymuştur;

Testlerin yürütülmesi sonucunda aşağıdaki sonuçlar elde edilmiştir:

Image

Testing started at 16:08 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/v04/tests/TestDaoMétier.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/v04/tests/TestDaoMétier.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v04\tests



Ran 11 tests in 0.055s

OK

Process finished with exit code 0

15.1.5. Ana komut dosyası

Image

Ana komut dosyası, aşağıdaki [config] komut dosyası ile yapılandırılmıştır:


def configure():
    import os

    # adım 1 ------
    # Python yolunun yapılandırılması

    # bu dosyanın bulunduğu klasör
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # root_dir
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"

    # uygulamanın bağımlılıkları
    absolute_dependencies = [
        # yerel bağımlılıklar
        f"{script_dir}/../../entities",
        f"{script_dir}/../../interfaces",
        f"{script_dir}/../../services",
        f"{root_dir}/02/entities",
    ]

    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama yapılandırması
    config = {
        # uygulama dosyalarının mutlak yolları
        "taxpayersFilename"f"{script_dir}/../../data/input/taxpayersdata.txt",
        "resultsFilename"f"{script_dir}/../../data/output/résultats.json",
        "admindataFilename"f"{script_dir}/../../data/input/admindata.json",
        "errorsFilename"f"{script_dir}/../../data/output/errors.txt"
    }

    # uygulama katmanlarının örneklenmesi
    from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
    from ImpôtsMétier import ImpôtsMétier

    dao = ImpôtsDaoWithAdminDataInJsonFile(config)
    métier = ImpôtsMétier()

    # katman örneklerini yapılandırmaya ekliyoruz
    config["dao"] = dao
    config["métier"] = métier

    # yapılandırmayı oluşturuyoruz
    return config

Bu komut dosyası, [métier] ve [dao] katmanlarının testinde kullanılan komut dosyasına benzerdir.

[main.py] ana komut dosyası şöyledir:


# uygulamayı yapılandırıyoruz
import config

config = config.configure()

# ithalatlar
from ImpôtsError import ImpôtsError

# uygulamanın katmanlarını alıyoruz (bunlar zaten örneklenmiş durumda)
dao = config["dao"]
métier = config["métier"]

try:
    # vergi dilimlerini alıyoruz
    admindata = dao.get_admindata()
    # vergi mükelleflerinin verilerinin okunması
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    # vergi mükelleflerinin mi?
    if not taxpayers:
        raise ImpôtsError(51, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    # Vergi mükelleflerinin vergisinin hesaplanması
    for taxpayer in taxpayers:
        # "vergi mükellefi" hem giriş hem de çıkış parametresidir
        # "taxpayer" değiştirilecek
        métier.calculate_tax(taxpayer, admindata)
    # sonuçların bir metin dosyasına yazılması
    dao.write_taxpayers_results(taxpayers)
except ImpôtsError as erreur:
    # hatanın görüntülenmesi
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    # işlem tamamlandı
    print("Travail terminé...")

Notlar

  • 2-4. satırlar: Uygulamanın yapılandırması alınır. Ayrıca, uygulamanın Python Yolu'nun oluşturulduğu da bilinmektedir;
  • 9-11. satırlar: [métier] ve [dao] katmanlarına ilişkin referanslar alınır;
    1. satır: Vergi idaresinden veriler alınır;
    1. satır: Vergisi hesaplanacak vergi mükelleflerinin listesi alınır;
  • 19-20. satırlar: Bu liste boşsa, bir istisna oluşturulur;
  • satır 22-25: [métier] katmanı kullanılarak çeşitli [taxpayer] nesnelerinin vergisi hesaplanır;
  • satır 27: [taxpayers] artık, özniteliklerine (vergi, indirim, ek vergi, indirim, oran) değer atanan [TaxPayer] nesnelerinden oluşan bir listedir. Bu liste, jSON dosyasına yazılır;
  • 28-30. satırlar: olası bir hatanın yakalanması;
  • 31-33. satırlar: her durumda yürütülür;

Komut dosyasının çalıştırılması, önceki sürümlerdekiyle aynı sonuçları verir. Vergi mükelleflerinin hata dosyası, bu sürümde yeni eklenmiştir. [main] komut dosyası çalıştırıldıktan sonra içeriği şu şekildedir:


Analyse du fichier C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v04\main\01/../../data/input/taxpayersdata.txt

Ligne 17, not enough values to unpack (expected 4, got 2)
Ligne 19, too many values to unpack (expected 4)
Ligne 21, MyException[1, L'identifiant d'une entité <class 'TaxPayer.TaxPayer'> doit être un entier >=0]

Hatalı satırlar şunlardı:

# geçerli veriler: kimlik numarası, evli, çocuk sayısı, maaş
1,oui,2,55555
2,oui,2,50000
3,oui,3,50000
4,non,2,100000
5,non,3,100000
6,oui,3,100000
7,oui,5,100000
8,non,0,100000
9,oui,2,30000
10,non,0,200000
11,oui,3,200000
# boş satırlar olabilir

# hatalı satırlar oluşturuluyor
# yeterli değer yok
11,12
# fazla değer var
12,oui,3,200000, x, y
# hatalı değerler
x,x,x,x

15.2. Sürüm 4 – Uygulama 2

Bu sürümde, vergi mükelleflerinin listesini kullanıcı klavye aracılığıyla girer. Uygulamanın mimarisi şu şekilde olacaktır:

Image

Yeni bir modül ortaya çıkıyor: kullanıcıyla etkileşime girecek olan [ui] (Kullanıcı Arayüzü) katmanı. Bu katman bir arayüze sahip olacak ve bir sınıf tarafından uygulanacaktır.

Image

15.2.1. [InterfaceImpôtsUi] arayüzü


# ithalatlar
from abc import ABC, abstractmethod


# InterfaceImpôtsUI arayüzü
class InterfaceImpôtsUi(ABC):
    # arayüzü uygulayan sınıfın çalıştırılması
    @abstractmethod
    def run(self):
        pass

[InterfaceImpôtsUi] arayüzü, 8-10. satırlarda yer alan tek bir yönteme sahip olacaktır. Arayüz burada bir konsol uygulamasıyla uygulanacaktır, ancak bir grafik kullanıcı arayüzüyle de uygulanabilir. [run] yöntemine geçirilen parametreler, her iki uygulamada da aynı olmayacaktır. Bu sorunu aşmak için genellikle şu yöntem kullanılır:

  • [run] yöntemine parametre vermemek (veya en az sayıda parametre vermek);
  • arayüzü uygulayan sınıfın oluşturucusuna parametreler aktarmak. Bu parametreler, uygulamadan uygulamaya farklılık gösterebilir. Bu parametreler, sınıfın öznitelikleri olarak kaydedilir;
  • [run] yönteminin bu sınıf özniteliklerini (self.x) kullanmasını sağlamak;

Bu yöntem, her bir uygulama sınıfının yapıcı parametreleriyle belirlenmiş çok genel bir arayüz elde etmeyi sağlar. Bu yöntem, modüler sürüm 1 için daha önce kullanılmıştır.

15.2.2. [ImpôtsConsole] sınıfı

[ImpôtsConsole] sınıfı, [InterfaceImpôtsUi] arayüzünü şu şekilde uygular:


# ithalatlar
import re

from InterfaceImpôtsUi import InterfaceImpôtsUi
from TaxPayer import TaxPayer


# [UI] katmanı
class ImpôtsConsole(InterfaceImpôtsUi):
    # yapıcı
    def __init__(self, config: dict):
        # parametrelerin kaydedilmesi
        self.admindata = config['dao'].get_admindata()
        self.métier = config['métier']

    def run(self):
        # kullanıcıyla etkileşimli diyalog
        fini = False
        while not fini:
            # Vergi mükellefi evli mi?
            marié = input("Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : ").strip().lower()
            # girişin geçerliliğinin kontrolü
            while marié != "oui" and marié != "non" and marié != "*":
                # hata mesajı
                print("Tapez oui ou non ou *")
                # soruyu tekrar sor
                marié = input("Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : ").strip().lower()
            # Bitti mi?
            if marié == "*":
                # diyalog sona erdi
                return
            # çocuk sayısı
            enfants = input("Nombre d'enfants : ").strip()
            # girişin geçerliliğinin kontrolü
            if not re.match(r"^\d+$", enfants):
                # hata mesajı
                print("Tapez un nombre entier positif ou nul")
                # yeniden başla
                enfants = input("Nombre d'enfants : ").strip()
            # yıllık maaş
            salaire = input("Salaire annuel : ").strip()
            # girişin geçerliliğinin kontrolü
            if not re.match(r"^\d+$", salaire):
                # hata mesajı
                print("Tapez un nombre entier positif ou nul")
                # yeniden deneme
                salaire = input("Salaire annuel : ").strip()
            # vergi hesaplaması
            taxpayer = TaxPayer().fromdict({'id': 0, 'marié': marié, 'enfants': int(enfants), 'salaire': int(salaire)})
            self.métier.calculate_tax(taxpayer, self.admindata)
            # görüntüleme
            print(f"Impôt du contribuable = {taxpayer}\n\n")
            # sonraki vergi mükellefi
    1. satır: [ImpôtsConsole] sınıfı, [InterfaceImpôtsUi] arayüzünü uygular;
    1. satır: Sınıfın oluşturucusu, uygulama yapılandırmasına ait [config] sözlüğünü bir parametre olarak alır;
      1. satır: Vergi hesaplamasına olanak tanıyan vergi idaresi verileri alınır;
      1. satır: [métier] katmanında bir referans kaydedilir;
    1. satır: arayüzün [run] yönteminin uygulanması;
  • satır 19-53: kullanıcıyla etkileşim. Bu etkileşim,
    • vergi mükellefinin üç bilgisini (evli olup olmadığı, çocuk sayısı, maaşı) istemek;
    • vergisini hesaplamak;
    • vergiyi ekrana yansıtmak;
    • kullanıcı ilk soruya * yanıt verdiğinde diyalog sona erer;
  • 20-27. satırlar: vergi mükellefinin evli olup olmadığı sorulur ve cevabın geçerliliği kontrol edilir;
  • 29-31. satırlar: Kullanıcı soruya ‘*’ ile yanıt verirse diyalog sonlandırılır;
  • satır 32-39: vergi mükellefinin çocuk sayısı sorulur ve cevabın geçerliliği kontrol edilir;
  • satır 40-47: vergi mükellefinin yıllık maaşı sorulur ve cevabın geçerliliği kontrol edilir;
  • satır 48-50: bu bilgilerle, [métier] katmanı aracılığıyla vergi mükellefinin vergisi hesaplanır;
    1. satır: vergi tutarı görüntülenir;

15.2.3. Ana komut dosyası

Ana komut dosyası [main], aşağıdaki [config] dosyası ile yapılandırılır:


def configure():
    import os

    # 1. adım ------
    # Python yolunun yapılandırılması

    # bu dosyanın bulunduğu klasör
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # root_dir
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"

    # uygulamanın bağımlılıkları
    absolute_dependencies = [
        # yerel bağımlılıklar
        f"{script_dir}/../../entities",
        f"{script_dir}/../../interfaces",
        f"{script_dir}/../../services",
        f"{root_dir}/02/entities",
    ]

    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama yapılandırması
    config = {
        # uygulama dosyalarının mutlak yolları
        "admindataFilename"f"{script_dir}/../../data/input/admindata.json",
    }

    # uygulama katmanlarının örneklenmesi
    from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
    from ImpôtsMétier import ImpôtsMétier
    from ImpôtsConsole import ImpôtsConsole

    # DAO katmanı
    dao = ImpôtsDaoWithAdminDataInJsonFile(config)
    # iş mantığı katmanı
    métier = ImpôtsMétier()
    # katman örneklerini yapılandırmaya ekliyoruz
    config["dao"] = dao
    config["métier"] = métier
    # UI katmanı
    ui = ImpôtsConsole(config)
    config["ui"] = ui

    # yapılandırmayı oluşturuyoruz
    return config

Koordinatör komut dosyası şöyledir (main.py):


# uygulamayı yapılandırıyoruz
import config

config = config.configure()

# ithalatlar
from ImpôtsError import ImpôtsError

# uygulamanın katmanlarını alıyoruz (bunlar zaten örneklenmiş durumda)
ui = config["ui"]

# kod
try:
    # [ui] katmanının çalıştırılması
    ui.run()
except ImpôtsError as erreur:
    # hata mesajı görüntülenir
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    # her durumda yürütülür
    print("Travail terminé...")
  • 1-4. satırlar: uygulamanın yapılandırması alınır;
    1. satır: [ui] katmanına ilişkin bir referans alınır;
  • 12-21. satırlar: kod yapısı önceki uygulamadakiyle aynıdır: olası istisnaları durdurmak için try / catch ile çevrelenmiş kod;
    1. satır: [ui] katmanının çalıştırılması istenir: böylece kullanıcıyla diyalog başlar;
  • 16-18. satırlar: Olası bir istisnanın yakalanması;

İşte bir yürütme örneği:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/v04/main/02/main.py
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : oui
Nombre d'enfants : 3
Salaire annuel : 200000
Impôt du contribuable = {"id": 0, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}


Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : *
Travail terminé...

Process finished with exit code 0