Exemple #1
0
class TransmissionStart(Record):
    """TransmissionStart is the first record in every OCR file.

    A file can only contain a single transmission.

    Each transmission can contain any number of assignments.
    """

    transmission_number = attr.ib(validator=str_of_length(7))
    data_transmitter = attr.ib(validator=str_of_length(8))
    data_recipient = attr.ib(validator=str_of_length(8))

    RECORD_TYPE = netsgiro.RecordType.TRANSMISSION_START
    _PATTERNS = [
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>00)
            00      # Transmission type, always 00
            10      # Record type

            (?P<data_transmitter>\d{8})
            (?P<transmission_number>\d{7})
            (?P<data_recipient>\d{8})

            0{49}   # Padding
            $
            ''',
            re.VERBOSE,
        )
    ]

    def to_ocr(self) -> str:
        """Get record as OCR string."""
        return (
            'NY000010'
            '{self.data_transmitter:8}'
            '{self.transmission_number:7}'
            '{self.data_recipient:8}' + ('0' * 49)
        ).format(self=self)
Exemple #2
0
class Transaction:
    """Transaction contains an OCR Giro transaction.

    Transactions are found in assignments with the service code
    :attr:`~netsgiro.ServiceCode.OCR_GIRO` type, which are only
    created by Nets.
    """

    #: The service code. One of :class:`~netsgiro.ServiceCode`.
    service_code = attr.ib(converter=netsgiro.ServiceCode)

    #: The transaction type. One of :class:`~netsgiro.TransactionType`.
    type = attr.ib(converter=netsgiro.TransactionType)

    #: Transaction number. Unique and ordered within an assignment.
    number = attr.ib(validator=instance_of(int))

    #: Nets' processing date.
    date = attr.ib(validator=instance_of(datetime.date))

    #: Transaction amount in NOK with two decimals.
    amount = attr.ib(converter=Decimal)

    #: KID number to identify the customer and invoice.
    kid = attr.ib(validator=optional(instance_of(str)))

    #: The value depends on the payment method.
    reference = attr.ib(validator=optional(instance_of(str)))

    #: Up to 40 chars of free text from the payment terminal.
    text = attr.ib(validator=optional(instance_of(str)))

    #: Used for OCR Giro.
    centre_id = attr.ib(validator=optional(str_of_length(2)))

    #: Used for OCR Giro.
    day_code = attr.ib(validator=optional(instance_of(int)))

    #: Used for OCR Giro.
    partial_settlement_number = attr.ib(validator=optional(instance_of(int)))

    #: Used for OCR Giro.
    partial_settlement_serial_number = attr.ib(
        validator=optional(str_of_length(5)))

    #: Used for OCR Giro.
    sign = attr.ib(validator=optional(str_of_length(1)))

    #: Used for OCR Giro.
    form_number = attr.ib(validator=optional(str_of_length(10)))

    #: Used for OCR Giro.
    bank_date = attr.ib(validator=optional(instance_of(datetime.date)))

    #: Used for OCR Giro.
    debit_account = attr.ib(validator=optional(str_of_length(11)))

    _filler = attr.ib(validator=optional(str_of_length(7)))

    @property
    def amount_in_cents(self) -> int:
        """Transaction amount in NOK cents."""
        return int(self.amount * 100)

    @classmethod
    def from_records(cls, records: List[Record]) -> 'Transaction':
        """Build a Transaction object from a list of record objects."""
        amount_item_1 = records.pop(0)
        assert isinstance(amount_item_1,
                          netsgiro.records.TransactionAmountItem1)
        amount_item_2 = records.pop(0)
        assert isinstance(amount_item_2,
                          netsgiro.records.TransactionAmountItem2)

        if len(records) == 1 and isinstance(
                records[0], netsgiro.records.TransactionAmountItem3):
            text = records[0].text
        else:
            text = None

        return cls(
            service_code=amount_item_1.service_code,
            type=amount_item_1.transaction_type,
            number=amount_item_1.transaction_number,
            date=amount_item_1.nets_date,
            amount=Decimal(amount_item_1.amount) / 100,
            kid=amount_item_1.kid,
            reference=amount_item_2.reference,
            text=text,
            centre_id=amount_item_1.centre_id,
            day_code=amount_item_1.day_code,
            partial_settlement_number=amount_item_1.partial_settlement_number,
            partial_settlement_serial_number=(
                amount_item_1.partial_settlement_serial_number),
            sign=amount_item_1.sign,
            form_number=amount_item_2.form_number,
            bank_date=amount_item_2.bank_date,
            debit_account=amount_item_2.debit_account,
            filler=amount_item_2._filler,
        )

    def to_records(self) -> Iterable[Record]:
        """Convert the transaction to a list of records."""
        yield netsgiro.records.TransactionAmountItem1(
            service_code=self.service_code,
            transaction_type=self.type,
            transaction_number=self.number,
            nets_date=self.date,
            amount=self.amount_in_cents,
            kid=self.kid,
            centre_id=self.centre_id,
            day_code=self.day_code,
            partial_settlement_number=self.partial_settlement_number,
            partial_settlement_serial_number=(
                self.partial_settlement_serial_number),
            sign=self.sign,
        )
        yield netsgiro.records.TransactionAmountItem2(
            service_code=self.service_code,
            transaction_type=self.type,
            transaction_number=self.number,
            reference=self.reference,
            form_number=self.form_number,
            bank_date=self.bank_date,
            debit_account=self.debit_account,
            filler=self._filler,
        )

        if self.type in (
                netsgiro.TransactionType.REVERSING_WITH_TEXT,
                netsgiro.TransactionType.PURCHASE_WITH_TEXT,
        ):
            yield netsgiro.records.TransactionAmountItem3(
                service_code=self.service_code,
                transaction_type=self.type,
                transaction_number=self.number,
                text=self.text,
            )
Exemple #3
0
class Transmission:
    """Transmission is the top-level object.

    An OCR file contains a single transmission. The transmission can contain
    multiple :class:`~netsgiro.Assignment` objects of various types.
    """

    #: Data transmitters unique enumeration of the transmission. String of 7
    #: digits.
    number = attr.ib(validator=str_of_length(7))

    #: Data transmitter's Nets ID. String of 8 digits.
    data_transmitter = attr.ib(validator=str_of_length(8))

    #: Data recipient's Nets ID. String of 8 digits.
    data_recipient = attr.ib(validator=str_of_length(8))

    #: For OCR Giro files from Nets, this is Nets' processing date.
    #:
    #: For AvtaleGiro payment request, the earliest due date in the
    #: transmission is automatically used.
    date = attr.ib(default=None,
                   validator=optional(instance_of(datetime.date)))

    #: List of assignments.
    assignments = attr.ib(default=attr.Factory(list), repr=False)

    @classmethod
    def from_records(cls, records: List[Record]) -> 'Transmission':
        """Build a Transmission object from a list of record objects."""
        if len(records) < 2:
            raise ValueError('At least 2 records required, got {}'.format(
                len(records)))

        start, body, end = records[0], records[1:-1], records[-1]

        assert isinstance(start, netsgiro.records.TransmissionStart)
        assert isinstance(end, netsgiro.records.TransmissionEnd)

        return cls(
            number=start.transmission_number,
            data_transmitter=start.data_transmitter,
            data_recipient=start.data_recipient,
            date=end.nets_date,
            assignments=cls._get_assignments(body),
        )

    @staticmethod
    def _get_assignments(records: List[Record]) -> List['Assignment']:
        assignments = collections.OrderedDict()

        current_assignment_number = None
        for record in records:
            if isinstance(record, netsgiro.records.AssignmentStart):
                current_assignment_number = record.assignment_number
                assignments[current_assignment_number] = []
            if current_assignment_number is None:
                raise ValueError(
                    'Expected AssignmentStart record, got {!r}'.format(record))
            assignments[current_assignment_number].append(record)
            if isinstance(record, netsgiro.records.AssignmentEnd):
                current_assignment_number = None

        return [Assignment.from_records(rs) for rs in assignments.values()]

    def to_ocr(self) -> str:
        """Convert the transmission to an OCR string."""
        lines = [record.to_ocr() for record in self.to_records()]
        return '\n'.join(lines)

    def to_records(self) -> Iterable[Record]:
        """Convert the transmission to a list of records."""
        yield self._get_start_record()
        for assignment in self.assignments:
            yield from assignment.to_records()
        yield self._get_end_record()

    def _get_start_record(self) -> Record:
        return netsgiro.records.TransmissionStart(
            service_code=netsgiro.ServiceCode.NONE,
            transmission_number=self.number,
            data_transmitter=self.data_transmitter,
            data_recipient=self.data_recipient,
        )

    def _get_end_record(self) -> Record:
        avtalegiro_payment_request = all(
            assignment.service_code == netsgiro.ServiceCode.AVTALEGIRO
            and assignment.type in (
                netsgiro.AssignmentType.TRANSACTIONS,
                netsgiro.AssignmentType.AVTALEGIRO_CANCELLATIONS,
            ) for assignment in self.assignments)
        if self.assignments and avtalegiro_payment_request:
            date = min(assignment.get_earliest_transaction_date()
                       for assignment in self.assignments)
        else:
            date = self.date

        return netsgiro.records.TransmissionEnd(
            service_code=netsgiro.ServiceCode.NONE,
            num_transactions=self.get_num_transactions(),
            num_records=self.get_num_records(),
            total_amount=int(self.get_total_amount() * 100),
            nets_date=date,
        )

    def add_assignment(self,
                       *,
                       service_code: netsgiro.ServiceCode,
                       assignment_type: netsgiro.AssignmentType,
                       agreement_id: Optional[str] = None,
                       number: str,
                       account: str,
                       date: Optional[datetime.date] = None) -> 'Assignment':
        """Add an assignment to the tranmission."""

        assignment = Assignment(
            service_code=service_code,
            type=assignment_type,
            agreement_id=agreement_id,
            number=number,
            account=account,
            date=date,
        )
        self.assignments.append(assignment)
        return assignment

    def get_num_transactions(self) -> int:
        """Get number of transactions in the transmission."""
        return sum(assignment.get_num_transactions()
                   for assignment in self.assignments)

    def get_num_records(self) -> int:
        """Get number of records in the transmission.

        Includes the transmission's start and end record.
        """
        return 2 + sum(assignment.get_num_records()
                       for assignment in self.assignments)

    def get_total_amount(self) -> Decimal:
        """Get the total amount from all transactions in the transmission."""
        return sum(assignment.get_total_amount()
                   for assignment in self.assignments)
Exemple #4
0
class Assignment:
    """An Assignment groups multiple transactions within a transmission.

    Use :meth:`netsgiro.Transmission.add_assignment` to create assignments.
    """

    #: The service code. One of :class:`~netsgiro.ServiceCode`.
    service_code = attr.ib(converter=netsgiro.ServiceCode)

    #: The transaction type. One of :class:`~netsgiro.TransactionType`.
    type = attr.ib(converter=netsgiro.AssignmentType)

    #: The assignment number. String of 7 digits.
    number = attr.ib(validator=str_of_length(7))

    #: The payee's bank account. String of 11 digits.
    account = attr.ib(validator=str_of_length(11))

    #: Used for OCR Giro.
    #:
    #: The payee's agreement ID with Nets. String of 9 digits.
    agreement_id = attr.ib(default=None, validator=optional(str_of_length(9)))

    #: Used for OCR Giro.
    #:
    #: The date the assignment was generated by Nets.
    date = attr.ib(default=None,
                   validator=optional(instance_of(datetime.date)))

    #: List of transaction objects, like :class:`~netsgiro.Agreement`,
    #: :class:`~netsgiro.PaymentRequest`, :class:`~netsgiro.Transaction`.
    transactions = attr.ib(default=attr.Factory(list), repr=False)

    _next_transaction_number = 1

    @classmethod
    def from_records(cls, records: List[Record]) -> 'Assignment':
        """Build an Assignment object from a list of record objects."""
        if len(records) < 2:
            raise ValueError('At least 2 records required, got {}'.format(
                len(records)))

        start, body, end = records[0], records[1:-1], records[-1]

        assert isinstance(start, netsgiro.records.AssignmentStart)
        assert isinstance(end, netsgiro.records.AssignmentEnd)

        if start.service_code == netsgiro.ServiceCode.AVTALEGIRO:
            if (start.assignment_type ==
                    netsgiro.AssignmentType.AVTALEGIRO_AGREEMENTS):
                transactions = cls._get_agreements(body)
            else:
                transactions = cls._get_payment_requests(body)
        elif start.service_code == netsgiro.ServiceCode.OCR_GIRO:
            transactions = cls._get_transactions(body)
        else:
            raise ValueError('Unknown service code: {}'.format(
                start.service_code))

        return cls(
            service_code=start.service_code,
            type=start.assignment_type,
            agreement_id=start.agreement_id,
            number=start.assignment_number,
            account=start.assignment_account,
            date=end.nets_date,
            transactions=transactions,
        )

    @staticmethod
    def _get_agreements(records: List[Record]) -> List['Agreement']:
        return [Agreement.from_records([r]) for r in records]

    @classmethod
    def _get_payment_requests(cls,
                              records: List[Record]) -> List['PaymentRequest']:
        transactions = cls._group_by_transaction_number(records)
        return [
            PaymentRequest.from_records(rs) for rs in transactions.values()
        ]

    @classmethod
    def _get_transactions(cls, records: List[Record]) -> List['Transaction']:
        transactions = cls._group_by_transaction_number(records)
        return [Transaction.from_records(rs) for rs in transactions.values()]

    @staticmethod
    def _group_by_transaction_number(
        records: List[Record], ) -> Mapping[int, List[Record]]:
        transactions = collections.OrderedDict()

        for record in records:
            if record.transaction_number not in transactions:
                transactions[record.transaction_number] = []
            transactions[record.transaction_number].append(record)

        return transactions

    def to_records(self) -> Iterable[Record]:
        """Convert the assignment to a list of records."""
        yield self._get_start_record()
        for transaction in self.transactions:
            yield from transaction.to_records()
        yield self._get_end_record()

    def _get_start_record(self) -> Record:
        return netsgiro.records.AssignmentStart(
            service_code=self.service_code,
            assignment_type=self.type,
            assignment_number=self.number,
            assignment_account=self.account,
            agreement_id=self.agreement_id,
        )

    def _get_end_record(self) -> Record:
        if self.service_code == netsgiro.ServiceCode.OCR_GIRO:
            dates = {
                'nets_date_1': self.date,
                'nets_date_2': self.get_earliest_transaction_date(),
                'nets_date_3': self.get_latest_transaction_date(),
            }
        elif self.service_code == netsgiro.ServiceCode.AVTALEGIRO:
            dates = {
                'nets_date_1': self.get_earliest_transaction_date(),
                'nets_date_2': self.get_latest_transaction_date(),
            }
        else:
            raise ValueError('Unhandled service code: {}'.format(
                self.service_code))

        return netsgiro.records.AssignmentEnd(
            service_code=self.service_code,
            assignment_type=self.type,
            num_transactions=self.get_num_transactions(),
            num_records=self.get_num_records(),
            total_amount=int(self.get_total_amount() * 100),
            **dates)

    def add_payment_request(
            self,
            *,
            kid: str,
            due_date: datetime.date,
            amount: Decimal,
            reference: Optional[str] = None,
            payer_name: Optional[str] = None,
            bank_notification: Union[bool, str] = False) -> 'Transaction':
        """Add an AvtaleGiro payment request to the assignment.

        The assignment must have service code
        :attr:`~netsgiro.ServiceCode.AVTALEGIRO` and assignment type
        :attr:`~netsgiro.AssignmentType.TRANSACTIONS`.
        """

        assert (self.service_code == netsgiro.ServiceCode.AVTALEGIRO
                ), 'Can only add payment requests to AvtaleGiro assignments'
        assert (self.type == netsgiro.AssignmentType.TRANSACTIONS
                ), 'Can only add payment requests to transaction assignments'

        if bank_notification:
            transaction_type = (
                netsgiro.TransactionType.AVTALEGIRO_WITH_BANK_NOTIFICATION)
        else:
            transaction_type = (
                netsgiro.TransactionType.AVTALEGIRO_WITH_PAYEE_NOTIFICATION)

        return self._add_avtalegiro_transaction(
            transaction_type=transaction_type,
            kid=kid,
            due_date=due_date,
            amount=amount,
            reference=reference,
            payer_name=payer_name,
            bank_notification=bank_notification,
        )

    def add_payment_cancellation(
            self,
            *,
            kid: str,
            due_date: datetime.date,
            amount: Decimal,
            reference: Optional[str] = None,
            payer_name: Optional[str] = None,
            bank_notification: Union[bool, str] = False) -> 'Transaction':
        """Add an AvtaleGiro cancellation to the assignment.

        The assignment must have service code
        :attr:`~netsgiro.ServiceCode.AVTALEGIRO` and assignment type
        :attr:`~netsgiro.AssignmentType.AVTALEGIRO_CANCELLATIONS`.

        Otherwise, the cancellation must be identical to the payment request it
        is cancelling.
        """

        assert (self.service_code == netsgiro.ServiceCode.AVTALEGIRO
                ), 'Can only add cancellation to AvtaleGiro assignments'
        assert (self.type == netsgiro.AssignmentType.AVTALEGIRO_CANCELLATIONS
                ), 'Can only add cancellation to cancellation assignments'

        return self._add_avtalegiro_transaction(
            transaction_type=netsgiro.TransactionType.AVTALEGIRO_CANCELLATION,
            kid=kid,
            due_date=due_date,
            amount=amount,
            reference=reference,
            payer_name=payer_name,
            bank_notification=bank_notification,
        )

    def _add_avtalegiro_transaction(self,
                                    *,
                                    transaction_type,
                                    kid,
                                    due_date,
                                    amount,
                                    reference=None,
                                    payer_name=None,
                                    bank_notification=None) -> 'Transaction':

        if isinstance(bank_notification, str):
            text = bank_notification
        else:
            text = ''

        number = self._next_transaction_number
        self._next_transaction_number += 1

        transaction = PaymentRequest(
            service_code=self.service_code,
            type=transaction_type,
            number=number,
            date=due_date,
            amount=amount,
            kid=kid,
            reference=reference,
            text=text,
            payer_name=payer_name,
        )
        self.transactions.append(transaction)
        return transaction

    def get_num_transactions(self) -> int:
        """Get number of transactions in the assignment."""
        return len(self.transactions)

    def get_num_records(self) -> int:
        """Get number of records in the assignment.

        Includes the assignment's start and end record.
        """

        return 2 + sum(
            len(list(transaction.to_records()))
            for transaction in self.transactions)

    def get_total_amount(self) -> Decimal:
        """Get the total amount from all transactions in the assignment."""
        transactions = [
            transaction for transaction in self.transactions
            if hasattr(transaction, 'amount')
        ]
        if not transactions:
            return Decimal(0)
        return sum(transaction.amount for transaction in transactions)

    def get_earliest_transaction_date(self) -> Optional[datetime.date]:
        """Get earliest date from the assignment's transactions."""
        transactions = [
            transaction for transaction in self.transactions
            if hasattr(transaction, 'date')
        ]
        if not transactions:
            return None
        return min(transaction.date for transaction in transactions)

    def get_latest_transaction_date(self) -> Optional[datetime.date]:
        """Get latest date from the assignment's transactions."""
        transactions = [
            transaction for transaction in self.transactions
            if hasattr(transaction, 'date')
        ]
        if not transactions:
            return None
        return max(transaction.date for transaction in transactions)
Exemple #5
0
class TransactionAmountItem2(TransactionRecord):
    """TransactionAmountItem2 is the second record of a transaction.

    The record is used both for AvtaleGiro and for OCR Giro.
    """

    # TODO Validate `reference` length, which depends on service code
    reference = attr.ib(converter=to_safe_str_or_none)

    # Only OCR Giro
    form_number = attr.ib(default=None, validator=optional(str_of_length(10)))
    bank_date = attr.ib(default=None, converter=to_date)
    debit_account = attr.ib(default=None, validator=optional(str_of_length(11)))
    # XXX In use in OCR Giro "from giro debited account" transactions in test
    # data, but documented as a filler field.
    _filler = attr.ib(default=None)

    # Only AvtaleGiro
    payer_name = attr.ib(default=None, converter=to_safe_str_or_none)

    RECORD_TYPE = netsgiro.RecordType.TRANSACTION_AMOUNT_ITEM_2
    _PATTERNS = [
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>09)
            (?P<transaction_type>\d{2})  # 10-21
            31      # Record type

            (?P<transaction_number>\d{7})
            (?P<form_number>\d{10})
            (?P<reference>\d{9})

            (?P<filler>.{7})  # XXX Documented as filler, in use in test data

            (?P<bank_date>\d{6})
            (?P<debit_account>\d{11})

            0{22}    # Filler
            $
            ''',
            re.VERBOSE,
        ),
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>21)
            (?P<transaction_type>\d{2})  # 02, 21, or 93
            31      # Record type

            (?P<transaction_number>\d{7})
            (?P<payer_name>.{10})

            [ ]{25} # Filler

            (?P<reference>.{25})

            0{5}    # Filler
            $
            ''',
            re.VERBOSE,
        ),
    ]

    def to_ocr(self) -> str:
        """Get record as OCR string."""
        common_fields = (
            'NY'
            '{self.service_code:02d}'
            '{self.transaction_type:02d}'
            '31'
            '{self.transaction_number:07d}'
        ).format(self=self)

        if self.service_code == netsgiro.ServiceCode.OCR_GIRO:
            service_fields = (
                '{self.form_number:10}'
                + (self.reference and '{self.reference:9}' or (' ' * 9))
                + (self._filler and '{self._filler:7}' or ('0' * 7))
                + (self.bank_date and '{self.bank_date:%d%m%y}' or '0' * 6)
                + '{self.debit_account:11}'
                + ('0' * 22)
            ).format(self=self)
        elif self.service_code == netsgiro.ServiceCode.AVTALEGIRO:
            service_fields = (
                (
                    self.payer_name
                    and '{:10}'.format(self.payer_name[:10])
                    or (' ' * 10)
                )
                + (' ' * 25)
                + (self.reference and '{self.reference:25}' or (' ' * 25))
                + ('0' * 5)
            ).format(self=self)
        else:
            service_fields = ' ' * 35

        return common_fields + service_fields
Exemple #6
0
class TransactionAmountItem1(TransactionRecord):
    """TransactionAmountItem1 is the first record of a transaction.

    The record is used both for AvtaleGiro and for OCR Giro.
    """

    nets_date = attr.ib(converter=to_date)
    amount = attr.ib(converter=int)
    kid = attr.ib(
        converter=to_safe_str_or_none, validator=optional(str_of_max_length(25))
    )

    # Only OCR Giro
    centre_id = attr.ib(default=None, validator=optional(str_of_length(2)))
    day_code = attr.ib(default=None, converter=value_or_none(int))
    partial_settlement_number = attr.ib(
        default=None, converter=value_or_none(int)
    )
    partial_settlement_serial_number = attr.ib(
        default=None, validator=optional(str_of_length(5))
    )
    sign = attr.ib(default=None, validator=optional(str_of_length(1)))

    RECORD_TYPE = netsgiro.RecordType.TRANSACTION_AMOUNT_ITEM_1
    _PATTERNS = [
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>09)
            (?P<transaction_type>\d{2})  # 10-21
            30      # Record type

            (?P<transaction_number>\d{7})
            (?P<nets_date>\d{6})

            (?P<centre_id>\d{2})
            (?P<day_code>\d{2})
            (?P<partial_settlement_number>\d{1})
            (?P<partial_settlement_serial_number>\d{5})
            (?P<sign>[-0]{1})

            (?P<amount>\d{17})
            (?P<kid>[\d ]{25})

            0{6}    # Filler
            $
            ''',
            re.VERBOSE,
        ),
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>21)
            (?P<transaction_type>\d{2})  # 02, 21, or 93
            30      # Record type

            (?P<transaction_number>\d{7})
            (?P<nets_date>\d{6})

            [ ]{11} # Filler

            (?P<amount>\d{17})
            (?P<kid>[\d ]{25})

            0{6}    # Filler
            $
            ''',
            re.VERBOSE,
        ),
    ]

    def to_ocr(self) -> str:
        """Get record as OCR string."""
        if self.service_code == netsgiro.ServiceCode.OCR_GIRO:
            ocr_giro_fields = (
                '{self.centre_id:2}'
                '{self.day_code:02d}'
                '{self.partial_settlement_number:01d}'
                '{self.partial_settlement_serial_number:5}'
                '{self.sign:1}'
            ).format(self=self)
        else:
            ocr_giro_fields = ' ' * 11

        return (
            'NY'
            '{self.service_code:02d}'
            '{self.transaction_type:02d}'
            '30'
            '{self.transaction_number:07d}'
            '{self.nets_date:%d%m%y}' + ocr_giro_fields + '{self.amount:017d}'
            '{self.kid:>25}' + ('0' * 6)
        ).format(self=self)
Exemple #7
0
class AssignmentStart(Record):
    """AssignmentStart is the first record of an assignment.

    Each assignment can contain any number of transactions.
    """

    assignment_type = attr.ib(converter=to_assignment_type)
    assignment_number = attr.ib(validator=str_of_length(7))
    assignment_account = attr.ib(validator=str_of_length(11))

    # Only for assignment_type == AssignmentType.TRANSACTIONS
    agreement_id = attr.ib(default=None, validator=optional(str_of_length(9)))

    RECORD_TYPE = netsgiro.RecordType.ASSIGNMENT_START
    _PATTERNS = [
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>(09|21))
            (?P<assignment_type>00)
            20      # Record type

            (?P<agreement_id>\d{9})
            (?P<assignment_number>\d{7})
            (?P<assignment_account>\d{11})

            0{45}   # Filler
            $
            ''',
            re.VERBOSE,
        ),
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>21)
            (?P<assignment_type>24)
            20      # Record type

            0{9}    # Filler

            (?P<assignment_number>\d{7})
            (?P<assignment_account>\d{11})

            0{45}   # Filler
            $
            ''',
            re.VERBOSE,
        ),
        re.compile(
            r'''
            ^
            NY      # Format code
            (?P<service_code>21)
            (?P<assignment_type>36)
            20      # Record type

            0{9}    # Filler

            (?P<assignment_number>\d{7})
            (?P<assignment_account>\d{11})

            0{45}   # Filler
            $
            ''',
            re.VERBOSE,
        ),
    ]

    def to_ocr(self) -> str:
        """Get record as OCR string."""
        return (
            'NY'
            '{self.service_code:02d}'
            '{self.assignment_type:02d}'
            '20'
            + (self.agreement_id and '{self.agreement_id:9}' or ('0' * 9))
            + '{self.assignment_number:7}'
            '{self.assignment_account:11}' + ('0' * 45)
        ).format(self=self)