<commit_before>async = require 'async'

moment = require 'moment'

BankOperation = require '../models/bankoperation'

# Object that will handle all the matching and linking operation depending a
# given model.
# For each given bills, it will compare if a bank operation looks to match it.
# If the amount and date matched, the bill binary is linked to the bank
# operation.
class BankOperationLinker


    constructor: (options) ->
        @log = options.log
        @model = options.model
        @identifier = options.identifier.toLowerCase()
        @amountDelta = options.amountDelta or 0.001
        @dateDelta = options.dateDelta or 15
        @minDateDelta = options.minDateDelta or @dateDelta
        @maxDateDelta = options.maxDateDelta or @dateDelta


    link: (entries, callback) ->
        async.eachSeries entries, @linkOperationIfExist, callback


    # For a given entry we look for an operation with same date and same
    # amount.
    linkOperationIfExist: (entry, callback) =>
        date = new Date (entry.paidDate || entry.date)
        startDate = moment(date).subtract @minDateDelta, 'days'
        endDate = moment(date).add @maxDateDelta, 'days'

        startkey = "#{startDate.format "YYYY-MM-DDT00:00:00.000"}Z"
        endkey = "#{endDate.format "YYYY-MM-DDT00:00:00.000"}Z"

        BankOperation.all {startkey, endkey}, (err, operations) =>
            return callback err if err
            @linkRightOperation operations, entry, callback


    # Look for the operation of which amount matches the entry amount
    # If an operation to link is found, we save the binary ID
    # and the file ID as an extra attribute of the operation.
    linkRightOperation: (operations, entry, callback) ->
        operationToLink = null

        try
            amount = Math.abs parseFloat entry.amount
        catch
            callback()
            return

        minAmountDelta = Infinity
        for operation in operations

            opAmount = Math.abs operation.amount
            amountDelta = Math.abs opAmount - amount

            if operation.title.toLowerCase().indexOf(@identifier) >= 0 and \
            amountDelta <= @amountDelta and \
            amountDelta <= minAmountDelta
                operationToLink = operation
                minAmountDelta = amountDelta

        if not operationToLink?
            callback()
        else
            @linkOperation operationToLink, entry, callback


    # Save the binary ID and the file ID as an extra attribute of the
    # operation.
    linkOperation: (operation, entry, callback) =>
        date = new Date entry.date
        key = "#{moment(date).format('YYYY-MM-DD')}T00:00:00.000Z"

        @model.request 'byDate', key: key, (err, entries) =>

            # We ignore error, no need to make fail the import for that.
            # We just log it.
            if err
                @log.raw err
                callback()

            else if entries.length is 0
                callback()

            else<commit_msg>Allow to have several identifiers for operation matching<commit_after>async = require 'async'

moment = require 'moment'

BankOperation = require '../models/bankoperation'

# Object that will handle all the matching and linking operation depending a
# given model.
# For each given bills, it will compare if a bank operation looks to match it.
# If the amount and date matched, the bill binary is linked to the bank
# operation.
class BankOperationLinker


    constructor: (options) ->
        @log = options.log
        @model = options.model
        if typeof(options.identifier) is 'string'
            @identifier = [options.identifier.toLowerCase()]
        else
            @identifier = options.identifier.map((id) -> id.toLowerCase())
        @amountDelta = options.amountDelta or 0.001
        @dateDelta = options.dateDelta or 15
        @minDateDelta = options.minDateDelta or @dateDelta
        @maxDateDelta = options.maxDateDelta or @dateDelta


    link: (entries, callback) ->
        async.eachSeries entries, @linkOperationIfExist, callback


    # For a given entry we look for an operation with same date and same
    # amount.
    linkOperationIfExist: (entry, callback) =>
        date = new Date (entry.paidDate || entry.date)
        startDate = moment(date).subtract @minDateDelta, 'days'
        endDate = moment(date).add @maxDateDelta, 'days'

        startkey = "#{startDate.format "YYYY-<KEY>"
        endkey = "#{endDate.format "YYYY-<KEY>"

        BankOperation.all {startkey, endkey}, (err, operations) =>
            return callback err if err
            @linkRightOperation operations, entry, callback


    # Look for the operation of which amount matches the entry amount
    # If an operation to link is found, we save the binary ID
    # and the file ID as an extra attribute of the operation.
    linkRightOperation: (operations, entry, callback) ->
        operationToLink = null

        try
            amount = Math.abs parseFloat entry.amount
        catch
            callback()
            return

        minAmountDelta = Infinity
        for operation in operations

            opAmount = Math.abs operation.amount
            amountDelta = Math.abs opAmount - amount

            # Select the operation to link based on the minimal amount
            # difference to the expected one and if the label matches one
            # of the possible labels (identifier)
            for identifier in @identifier
                if operation.title.toLowerCase().indexOf(identifier) >= 0 and \
                amountDelta <= @amountDelta and \
                amountDelta <= minAmountDelta
                    operationToLink = operation
                    minAmountDelta = amountDelta
                    break

        if not operationToLink?
            callback()
        else
            @linkOperation operationToLink, entry, callback


    # Save the binary ID and the file ID as an extra attribute of the
    # operation.
    linkOperation: (operation, entry, callback) =>
        date = new Date entry.date
        key = "#{moment(date).format('YYYY-MM-DD')}T00:00:00.000Z"

        @model.request 'byDate', key: key, (err, entries) =>

            # We ignore error, no need to make fail the import for that.
            # We just log it.
            if err
                @log.raw err
                callback()

            else if entries.length is 0
                callback()

            else
{'fraction_non_alphanumeric': 0.0783253514599104, 'fraction_numerical': 0.00803336938050363, 'mean_word_length': 2.168869309838473, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3614089', 'n_tokens_mistral': 1677, 'n_tokens_neox': 1594, 'n_words': 603}