How to use match_Other method in Cucumber-gherkin

Best JavaScript code snippet using cucumber-gherkin

payment_model.js

Source:payment_model.js Github

copy

Full Screen

1odoo.define('base_accounting_kit.ReconciliationModel', function (require) {2"use strict";3var BasicModel = require('web.BasicModel');4var field_utils = require('web.field_utils');5var utils = require('web.utils');6var session = require('web.session');7var WarningDialog = require('web.CrashManager').WarningDialog;8var core = require('web.core');9var _t = core._t;10/**11 * Model use to fetch, format and update 'account.reconciliation.widget',12 * datas allowing reconciliation13 *14 * The statement internal structure::15 *16 *  {17 *      valuenow: integer18 *      valuenow: valuemax19 *      [bank_statement_line_id]: {20 *          id: integer21 *          display_name: string22 *      }23 *      reconcileModels: [object]24 *      accounts: {id: code}25 *  }26 *27 * The internal structure of each line is::28 *29 *   {30 *      balance: {31 *          type: number - show/hide action button32 *          amount: number - real amount33 *          amount_str: string - formated amount34 *          account_code: string35 *      },36 *      st_line: {37 *          partner_id: integer38 *          partner_name: string39 *      }40 *      mode: string ('inactive', 'match_rp', 'match_other', 'create')41 *      reconciliation_proposition: {42 *          id: number|string43 *          partial_amount: number44 *          invalid: boolean - through the invalid line (without account, label...)45 *          account_code: string46 *          date: string47 *          date_maturity: string48 *          label: string49 *          amount: number - real amount50 *          amount_str: string - formated amount51 *          [already_paid]: boolean52 *          [partner_id]: integer53 *          [partner_name]: string54 *          [account_code]: string55 *          [journal_id]: {56 *              id: integer57 *              display_name: string58 *          }59 *          [ref]: string60 *          [is_partially_reconciled]: boolean61 *          [to_check]: boolean62 *          [amount_currency_str]: string|false (amount in record currency)63 *      }64 *      mv_lines_match_rp: object - idem than reconciliation_proposition65 *      mv_lines_match_other: object - idem than reconciliation_proposition66 *      limitMoveLines: integer67 *      filter: string68 *      [createForm]: {69 *          account_id: {70 *              id: integer71 *              display_name: string72 *          }73 *          tax_ids: {74 *              id: integer75 *              display_name: string76 *          }77 *          analytic_account_id: {78 *              id: integer79 *              display_name: string80 *          }81 *          analytic_tag_ids: {82 *          }83 *          label: string84 *          amount: number,85 *          [journal_id]: {86 *              id: integer87 *              display_name: string88 *          }89 *      }90 *   }91 */92var StatementModel = BasicModel.extend({93    avoidCreate: false,94    quickCreateFields: ['account_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'to_check'],95    // overridden in ManualModel96    modes: ['create', 'match_rp', 'match_other'],97    /**98     * @override99     *100     * @param {Widget} parent101     * @param {object} options102     */103    init: function (parent, options) {104        this._super.apply(this, arguments);105        this.reconcileModels = [];106        this.lines = {};107        this.valuenow = 0;108        this.valuemax = 0;109        this.alreadyDisplayed = [];110        this.domain = [];111        this.defaultDisplayQty = options && options.defaultDisplayQty || 10;112        this.limitMoveLines = options && options.limitMoveLines || 15;113        this.display_context = 'init';114    },115    //--------------------------------------------------------------------------116    // Public117    //--------------------------------------------------------------------------118    /**119     * add a reconciliation proposition from the matched lines120     * We also display a warning if the user tries to add 2 line with different121     * account type122     *123     * @param {string} handle124     * @param {number} mv_line_id125     * @returns {Promise}126     */127    addProposition: function (handle, mv_line_id) {128        var self = this;129        var line = this.getLine(handle);130        var prop = _.clone(_.find(line['mv_lines_'+line.mode], {'id': mv_line_id}));131        this._addProposition(line, prop);132        line['mv_lines_'+line.mode] = _.filter(line['mv_lines_'+line.mode], l => l['id'] != mv_line_id);133        // remove all non valid lines134        line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (prop) {return prop && !prop.invalid;});135        // Onchange the partner if not already set on the statement line.136        if(!line.st_line.partner_id && line.reconciliation_proposition137            && line.reconciliation_proposition.length == 1 && prop.partner_id && line.type === undefined){138            return this.changePartner(handle, {'id': prop.partner_id, 'display_name': prop.partner_name}, true);139        }140        return Promise.all([141            this._computeLine(line),142            this._performMoveLine(handle, 'match_rp', line.mode == 'match_rp'? 1 : 0),143            this._performMoveLine(handle, 'match_other', line.mode == 'match_other'? 1 : 0)144        ]);145    },146    /**147     * change the filter for the target line and fetch the new matched lines148     *149     * @param {string} handle150     * @param {string} filter151     * @returns {Promise}152     */153    changeFilter: function (handle, filter) {154        var line = this.getLine(handle);155        line['filter_'+line.mode] = filter;156        line['mv_lines_'+line.mode] = [];157        return this._performMoveLine(handle, line.mode);158    },159    /**160     * change the mode line ('inactive', 'match_rp', 'match_other', 'create'),161     * and fetch the new matched lines or prepare to create a new line162     *163     * ``match_rp``164     *   display the matched lines from receivable/payable accounts, the user165     *   can select the lines to apply there as proposition166     * ``match_other``167     *   display the other matched lines, the user can select the lines to apply168     *   there as proposition169     * ``create``170     *   display fields and quick create button to create a new proposition171     *   for the reconciliation172     *173     * @param {string} handle174     * @param {'inactive' | 'match_rp' | 'create'} mode175     * @returns {Promise}176     */177    changeMode: function (handle, mode) {178        var self = this;179        var line = this.getLine(handle);180        if (mode === 'default') {181            var match_requests = self.modes.filter(x => x.startsWith('match')).map(x => this._performMoveLine(handle, x))182            return Promise.all(match_requests).then(function() {183                return self.changeMode(handle, self._getDefaultMode(handle));184            });185        }186        if (mode === 'next') {187            var available_modes = self._getAvailableModes(handle)188            mode = available_modes[(available_modes.indexOf(line.mode) + 1) % available_modes.length];189        }190        line.mode = mode;191        if (['match_rp', 'match_other'].includes(line.mode)) {192            if (!(line['mv_lines_' + line.mode] && line['mv_lines_' + line.mode].length)) {193                return this._performMoveLine(handle, line.mode);194            } else {195                return this._formatMoveLine(handle, line.mode, []);196            }197        }198        if (line.mode === 'create') {199            return this.createProposition(handle);200        }201        return Promise.resolve();202    },203    /**204     * fetch the more matched lines205     *206     * @param {string} handle207     * @returns {Promise}208     */209    changeOffset: function (handle) {210        var line = this.getLine(handle);211        return this._performMoveLine(handle, line.mode);212    },213    /**214     * change the partner on the line and fetch the new matched lines215     *216     * @param {string} handle217     * @param {bool} preserveMode218     * @param {Object} partner219     * @param {string} partner.display_name220     * @param {number} partner.id221     * @returns {Promise}222     */223    changePartner: function (handle, partner, preserveMode) {224        var self = this;225        var line = this.getLine(handle);226        line.st_line.partner_id = partner && partner.id;227        line.st_line.partner_name = partner && partner.display_name || '';228        line.mv_lines_match_rp = [];229        line.mv_lines_match_other = [];230        return Promise.resolve(partner && this._changePartner(handle, partner.id))231                .then(function() {232                    if(line.st_line.partner_id){233                        _.each(line.reconciliation_proposition, function(prop){234                            if(prop.partner_id != line.st_line.partner_id){235                                line.reconciliation_proposition = [];236                                return false;237                            }238                        });239                    }240                    return self._computeLine(line);241                })242                .then(function () {243                    return self.changeMode(handle, preserveMode ? line.mode : 'default', true);244                })245    },246    /**247     * close the statement248     * @returns {Promise<number>} resolves to the res_id of the closed statements249     */250    closeStatement: function () {251        var self = this;252        return this._rpc({253                model: 'account.bank.statement.line',254                method: 'button_confirm_bank',255                args: [self.bank_statement_line_id.id],256            })257            .then(function () {258                return self.bank_statement_line_id.id;259            });260    },261    /**262     *263     * then open the first available line264     *265     * @param {string} handle266     * @returns {Promise}267     */268    createProposition: function (handle) {269        var line = this.getLine(handle);270        var prop = _.filter(line.reconciliation_proposition, '__focus');271        prop = this._formatQuickCreate(line);272        line.reconciliation_proposition.push(prop);273        line.createForm = _.pick(prop, this.quickCreateFields);274        return this._computeLine(line);275    },276    /**277     * Return context information and journal_id278     * @returns {Object} context279     */280    getContext: function () {281        return this.context;282    },283    /**284     * Return the lines that needs to be displayed by the widget285     *286     * @returns {Object} lines that are loaded and not yet displayed287     */288    getStatementLines: function () {289        var self = this;290        var linesToDisplay = _.pick(this.lines, function(value, key, object) {291            if (value.visible === true && self.alreadyDisplayed.indexOf(key) === -1) {292                self.alreadyDisplayed.push(key);293                return object;294            }295        });296        return linesToDisplay;297    },298    /**299     * Return a boolean telling if load button needs to be displayed or not300     * overridden in ManualModel301     *302     * @returns {boolean} true if load more button needs to be displayed303     */304    hasMoreLines: function () {305        var notDisplayed = _.filter(this.lines, function(line) { return !line.visible; });306        if (notDisplayed.length > 0) {307            return true;308        }309        return false;310    },311    /**312     * get the line data for this handle313     *314     * @param {Object} handle315     * @returns {Object}316     */317    getLine: function (handle) {318        return this.lines[handle];319    },320    /**321     * load data from322     *323     * - 'account.bank.statement' fetch the line id and bank_statement_id info324     * - 'account.reconcile.model'  fetch all reconcile model (for quick add)325     * - 'account.account' fetch all account code326     * - 'account.reconciliation.widget' fetch each line data327     *328     * overridden in ManualModel329     * @param {Object} context330     * @param {number[]} context.statement_line_ids331     * @returns {Promise}332     */333    load: function (context) {334        var self = this;335        this.context = context;336        this.statement_line_ids = context.statement_line_ids;337        if (this.statement_line_ids === undefined) {338            // This could be undefined if the user pressed F5, take everything as fallback instead of rainbowman339            return self._rpc({340                model: 'account.bank.statement.line',341                method: 'search_read',342                fields: ['id'],343                domain: [['journal_id', '=?', context.active_id]],344            }).then(function (result) {345                self.statement_line_ids = result.map(r => r.id);346                return self.reload()347            })348        } else {349            return self.reload();350        }351    },352    /**353     * Load more bank statement line354     *355     * @param {integer} qty quantity to load356     * @returns {Promise}357     */358    loadMore: function(qty) {359        if (qty === undefined) {360            qty = this.defaultDisplayQty;361        }362        var ids = _.pluck(this.lines, 'id');363        ids = ids.splice(this.pagerIndex, qty);364        this.pagerIndex += qty;365        return this.loadData(ids, this._getExcludedIds());366    },367    /**368     * RPC method to load informations on lines369     * overridden in ManualModel370     *371     * @param {Array} ids ids of bank statement line passed to rpc call372     * @param {Array} excluded_ids list of move_line ids that needs to be excluded from search373     * @returns {Promise}374     */375    loadData: function(ids) {376        var self = this;377        var excluded_ids = this._getExcludedIds();378        return self._rpc({379            model: 'account.reconciliation.widget',380            method: 'get_bank_statement_line_data',381            args: [ids, excluded_ids],382            context: self.context,383        })384        .then(function(res){385            return self._formatLine(res['lines']);386        })387    },388    /**389     * Reload all data390     */391    reload: function() {392        var self = this;393        self.alreadyDisplayed = [];394        self.lines = {};395        self.pagerIndex = 0;396        var def_statement = this._rpc({397                model: 'account.reconciliation.widget',398                method: 'get_bank_statement_data',399                kwargs: {"bank_statement_line_ids":self.statement_line_ids, "srch_domain":self.domain},400                context: self.context,401            })402            .then(function (statement) {403                self.statement = statement;404                self.bank_statement_line_id = self.statement_line_ids.length === 1 ? {id: self.statement_line_ids[0], display_name: statement.statement_name} : false;405                self.valuenow = self.valuenow || statement.value_min;406                self.valuemax = self.valuemax || statement.value_max;407                self.context.journal_id = statement.journal_id;408                _.each(statement.lines, function (res) {409                    var handle = _.uniqueId('rline');410                    self.lines[handle] = {411                        id: res.st_line.id,412                        partner_id: res.st_line.partner_id,413                        handle: handle,414                        reconciled: false,415                        mode: 'inactive',416                        mv_lines_match_rp: [],417                        mv_lines_match_other: [],418                        filter_match_rp: "",419                        filter_match_other: "",420                        reconciliation_proposition: [],421                        reconcileModels: [],422                    };423                });424            });425        var domainReconcile = [];426        if (self.context && self.context.company_ids) {427            domainReconcile.push(['company_id', 'in', self.context.company_ids]);428        }429        if (self.context && self.context.active_model === 'account.journal' && self.context.active_ids) {430            domainReconcile.push('|');431            domainReconcile.push(['match_journal_ids', '=', false]);432            domainReconcile.push(['match_journal_ids', 'in', self.context.active_ids]);433        }434        var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile});435        var def_account = this._rpc({436                model: 'account.account',437                method: 'search_read',438                fields: ['code'],439            })440            .then(function (accounts) {441                self.accounts = _.object(_.pluck(accounts, 'id'), _.pluck(accounts, 'code'));442            });443        var def_taxes = self._loadTaxes();444        return Promise.all([def_statement, def_reconcileModel, def_account, def_taxes]).then(function () {445            _.each(self.lines, function (line) {446                line.reconcileModels = self.reconcileModels;447            });448            var ids = _.pluck(self.lines, 'id');449            ids = ids.splice(0, self.defaultDisplayQty);450            self.pagerIndex = ids.length;451            return self._formatLine(self.statement.lines);452        });453    },454    _readAnalyticTags: function (params) {455        var self = this;456        this.analyticTags = {};457        if (!params || !params.res_ids || !params.res_ids.length) {458            return $.when();459        }460        var fields = (params && params.fields || []).concat(['id', 'display_name']);461        return this._rpc({462                model: 'account.analytic.tag',463                method: 'read',464                args: [465                    params.res_ids,466                    fields,467                ],468            }).then(function (tags) {469                for (var i=0; i<tags.length; i++) {470                    var tag = tags[i];471                    self.analyticTags[tag.id] = tag;472                }473            });474    },475    _loadReconciliationModel: function (params) {476        var self = this;477        return this._rpc({478                model: 'account.reconcile.model',479                method: 'search_read',480                domain: params.domainReconcile || [],481            })482            .then(function (reconcileModels) {483               var analyticTagIds = [];484                for (var i=0; i<reconcileModels.length; i++) {485                    var modelTags = reconcileModels[i].analytic_tag_ids || [];486                    for (var j=0; j<modelTags.length; j++) {487                        if (analyticTagIds.indexOf(modelTags[j]) === -1) {488                            analyticTagIds.push(modelTags[j]);489                        }490                    }491                }492                return self._readAnalyticTags({res_ids: analyticTagIds}).then(function () {493                    for (var i=0; i<reconcileModels.length; i++) {494                        var recModel = reconcileModels[i];495                        var analyticTagData = [];496                        var modelTags = reconcileModels[i].analytic_tag_ids || [];497                        for (var j=0; j<modelTags.length; j++) {498                            var tagId = modelTags[j];499                            analyticTagData.push([tagId, self.analyticTags[tagId].display_name])500                        }501                        recModel.analytic_tag_ids = analyticTagData;502                    }503                    self.reconcileModels = reconcileModels;504                });505            });506    },507    _loadTaxes: function(){508        var self = this;509        self.taxes = {};510        return this._rpc({511                model: 'account.tax',512                method: 'search_read',513                fields: ['price_include', 'name'],514            }).then(function (taxes) {515                _.each(taxes, function(tax){516                    self.taxes[tax.id] = {517                        price_include: tax.price_include,518                        display_name: tax.name,519                    };520                });521                return taxes;522            });523    },524    /**525     * Add lines into the propositions from the reconcile model526     * Can add 2 lines, and each with its taxes. The second line become editable527     * in the create mode.528     *529     * @see 'updateProposition' method for more informations about the530     * 'amount_type'531     *532     * @param {string} handle533     * @param {integer} reconcileModelId534     * @returns {Promise}535     */536    quickCreateProposition: function (handle, reconcileModelId) {537        var self = this;538        var line = this.getLine(handle);539        var reconcileModel = _.find(this.reconcileModels, function (r) {return r.id === reconcileModelId;});540        var fields = ['account_id', 'amount', 'amount_type', 'analytic_account_id', 'journal_id', 'label', 'force_tax_included', 'tax_ids', 'analytic_tag_ids', 'to_check', 'amount_from_label_regex', 'decimal_separator'];541        this._blurProposition(handle);542        var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields));543        focus.reconcileModelId = reconcileModelId;544        line.reconciliation_proposition.push(focus);545        var defs = [];546        if (reconcileModel.has_second_line) {547            defs.push(self._computeLine(line).then(function() {548                var second = {};549                _.each(fields, function (key) {550                    second[key] = ("second_"+key) in reconcileModel ? reconcileModel["second_"+key] : reconcileModel[key];551                });552                var second_focus = self._formatQuickCreate(line, second);553                second_focus.reconcileModelId = reconcileModelId;554                line.reconciliation_proposition.push(second_focus);555                self._computeReconcileModels(handle, reconcileModelId);556            }))557        }558        return Promise.all(defs).then(function() {559            line.createForm = _.pick(focus, self.quickCreateFields);560            return self._computeLine(line);561        })562    },563    /**564     * Remove a proposition and switch to an active mode ('create' or 'match_rp' or 'match_other')565     * overridden in ManualModel566     *567     * @param {string} handle568     * @param {number} id (move line id)569     * @returns {Promise}570     */571    removeProposition: function (handle, id) {572        var self = this;573        var line = this.getLine(handle);574        var defs = [];575        var prop = _.find(line.reconciliation_proposition, {'id' : id});576        if (prop) {577            line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) {578                return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link);579            });580            if (prop['reconcileModelId'] === undefined) {581                if (['receivable', 'payable', 'liquidity'].includes(prop.account_type)) {582                    line.mv_lines_match_rp.unshift(prop);583                } else {584                    line.mv_lines_match_other.unshift(prop);585                }586            }587            // No proposition left and then, reset the st_line partner.588            if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner)589                defs.push(self.changePartner(line.handle));590        }591        line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match_rp';592        defs.push(this._computeLine(line));593        return Promise.all(defs).then(function() {594            return self.changeMode(handle, line.mode, true);595        })596    },597    getPartialReconcileAmount: function(handle, data) {598        var line = this.getLine(handle);599        var formatOptions = {600            currency_id: line.st_line.currency_id,601            noSymbol: true,602        };603        var prop = _.find(line.reconciliation_proposition, {'id': data.data});604        if (prop) {605            var amount = prop.partial_amount || prop.amount;606            // Check if we can get a partial amount that would directly set balance to zero607            var partial = Math.abs(line.balance.amount + amount);608            if (Math.abs(line.balance.amount) >= Math.abs(amount)) {609                amount = Math.abs(amount);610            } else if (partial <= Math.abs(prop.amount) && partial >= 0) {611                amount = partial;612            } else {613                amount = Math.abs(amount);614            }615            return field_utils.format.monetary(amount, {}, formatOptions);616        }617    },618    /**619     * Force the partial reconciliation to display the reconciliate button.620     *621     * @param {string} handle622     * @returns {Promise}623     */624    partialReconcile: function(handle, data) {625        var line = this.getLine(handle);626        var prop = _.find(line.reconciliation_proposition, {'id' : data.mvLineId});627        if (prop) {628            var amount = data.amount;629            try {630                amount = field_utils.parse.float(data.amount);631            }632            catch (err) {633                amount = NaN;634            }635            // Amount can't be greater than line.amount and can not be negative and must be a number636            // the amount we receive will be a string, so take sign of previous line amount in consideration in order to put637            // the amount in the correct left or right column638            if (amount >= Math.abs(prop.amount) || amount <= 0 || isNaN(amount)) {639                delete prop.partial_amount_str;640                delete prop.partial_amount;641                if (isNaN(amount) || amount < 0) {642                    this.do_warn(_.str.sprintf(_t('The amount %s is not a valid partial amount'), data.amount));643                }644                return this._computeLine(line);645            }646            else {647                var format_options = { currency_id: line.st_line.currency_id };648                prop.partial_amount = (prop.amount > 0 ? 1 : -1)*amount;649                prop.partial_amount_str = field_utils.format.monetary(Math.abs(prop.partial_amount), {}, format_options);650            }651        }652        return this._computeLine(line);653    },654    /**655     * Change the value of the editable proposition line or create a new one.656     *657     * If the editable line comes from a reconcile model with 2 lines658     * and their 'amount_type' is "percent"659     * and their total equals 100% (this doesn't take into account the taxes660     * who can be included or not)661     * Then the total is recomputed to have 100%.662     *663     * @param {string} handle664     * @param {*} values665     * @returns {Promise}666     */667    updateProposition: function (handle, values) {668        var self = this;669        var line = this.getLine(handle);670        var prop = _.last(_.filter(line.reconciliation_proposition, '__focus'));671        if ('to_check' in values && values.to_check === false) {672            // check if we have another line with to_check and if yes don't change value of this proposition673            prop.to_check = line.reconciliation_proposition.some(function(rec_prop, index) {674                return rec_prop.id !== prop.id && rec_prop.to_check;675            });676        }677        if (!prop) {678            prop = this._formatQuickCreate(line);679            line.reconciliation_proposition.push(prop);680        }681        _.each(values, function (value, fieldName) {682            if (fieldName === 'analytic_tag_ids') {683                switch (value.operation) {684                    case "ADD_M2M":685                        // handle analytic_tag selection via drop down (single dict) and686                        // full widget (array of dict)687                        var vids = _.isArray(value.ids) ? value.ids : [value.ids];688                        _.each(vids, function (val) {689                            if (!_.findWhere(prop.analytic_tag_ids, {id: val.id})) {690                                prop.analytic_tag_ids.push(val);691                            }692                        });693                        break;694                    case "FORGET":695                        var id = self.localData[value.ids[0]].ref;696                        prop.analytic_tag_ids = _.filter(prop.analytic_tag_ids, function (val) {697                            return val.id !== id;698                        });699                        break;700                }701            }702            else if (fieldName === 'tax_ids') {703                switch(value.operation) {704                    case "ADD_M2M":705                        prop.__tax_to_recompute = true;706                        var vids = _.isArray(value.ids) ? value.ids : [value.ids];707                        _.each(vids, function(val){708                            if (!_.findWhere(prop.tax_ids, {id: val.id})) {709                                value.ids.price_include = self.taxes[val.id] ? self.taxes[val.id].price_include : false;710                                prop.tax_ids.push(val);711                            }712                        });713                        break;714                    case "FORGET":715                        prop.__tax_to_recompute = true;716                        var id = self.localData[value.ids[0]].ref;717                        prop.tax_ids = _.filter(prop.tax_ids, function (val) {718                            return val.id !== id;719                        });720                        break;721                }722            }723            else {724                prop[fieldName] = values[fieldName];725            }726        });727        if ('account_id' in values) {728            prop.account_code = prop.account_id ? this.accounts[prop.account_id.id] : '';729        }730        if ('amount' in values) {731            prop.base_amount = values.amount;732            if (prop.reconcileModelId) {733                this._computeReconcileModels(handle, prop.reconcileModelId);734            }735        }736        if ('force_tax_included' in values || 'amount' in values || 'account_id' in values) {737            prop.__tax_to_recompute = true;738        }739        line.createForm = _.pick(prop, this.quickCreateFields);740        // If you check/uncheck the force_tax_included box, reset the createForm amount.741        if(prop.base_amount)742            line.createForm.amount = prop.base_amount;743        if (prop.tax_ids.length !== 1 ) {744            // When we have 0 or more than 1 taxes, reset the base_amount and force_tax_included, otherwise weird behavior can happen745            prop.amount = prop.base_amount;746            line.createForm.force_tax_included = false;747        }748        return this._computeLine(line);749    },750    /**751     * Format the value and send it to 'account.reconciliation.widget' model752     * Update the number of validated lines753     * overridden in ManualModel754     *755     * @param {(string|string[])} handle756     * @returns {Promise<Object>} resolved with an object who contains757     *   'handles' key758     */759    validate: function (handle) {760        var self = this;761        this.display_context = 'validate';762        var handles = [];763        if (handle) {764            handles = [handle];765        } else {766            _.each(this.lines, function (line, handle) {767                if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) {768                    handles.push(handle);769                }770            });771        }772        var ids = [];773        var values = [];774        var handlesPromises = [];775        _.each(handles, function (handle) {776            var line = self.getLine(handle);777            var props = _.filter(line.reconciliation_proposition, function (prop) {return !prop.invalid;});778            var computeLinePromise;779            if (props.length === 0) {780                // Usability: if user has not chosen any lines and click validate, it has the same behavior781                // as creating a write-off of the same amount.782                props.push(self._formatQuickCreate(line, {783                    account_id: [line.st_line.open_balance_account_id, self.accounts[line.st_line.open_balance_account_id]],784                }));785                // update balance of line otherwise it won't be to zero and another line will be added786                line.reconciliation_proposition.push(props[0]);787                computeLinePromise = self._computeLine(line);788            }789            ids.push(line.id);790            handlesPromises.push(Promise.resolve(computeLinePromise).then(function() {791                var values_dict = {792                    "partner_id": line.st_line.partner_id,793                    "counterpart_aml_dicts": _.map(_.filter(props, function (prop) {794                        return !isNaN(prop.id) && !prop.already_paid;795                    }), self._formatToProcessReconciliation.bind(self, line)),796                    "payment_aml_ids": _.pluck(_.filter(props, function (prop) {797                        return !isNaN(prop.id) && prop.already_paid;798                    }), 'id'),799                    "new_aml_dicts": _.map(_.filter(props, function (prop) {800                        return isNaN(prop.id) && prop.display;801                    }), self._formatToProcessReconciliation.bind(self, line)),802                    "to_check": line.to_check,803                };804                // If the lines are not fully balanced, create an unreconciled amount.805                // line.st_line.currency_id is never false here because its equivalent to806                // statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side).807                // see: get_statement_line_for_reconciliation_widget method in account/models/account_bank_statement.py for more details808                var currency = session.get_currency(line.st_line.currency_id);809                var balance = line.balance.amount;810                if (!utils.float_is_zero(balance, currency.digits[1])) {811                    var unreconciled_amount_dict = {812                        'account_id': line.st_line.open_balance_account_id,813                        'credit': balance > 0 ? balance : 0,814                        'debit': balance < 0 ? -balance : 0,815                        'name': line.st_line.name + ' : ' + _t("Open balance"),816                    };817                    values_dict['new_aml_dicts'].push(unreconciled_amount_dict);818                }819                values.push(values_dict);820                line.reconciled = true;821            }));822            _.each(self.lines, function(other_line) {823                if (other_line != line) {824                    var filtered_prop = other_line.reconciliation_proposition.filter(p => !line.reconciliation_proposition.map(l => l.id).includes(p.id));825                    if (filtered_prop.length != other_line.reconciliation_proposition.length) {826                        other_line.need_update = true;827                        other_line.reconciliation_proposition = filtered_prop;828                    }829                    self._computeLine(line);830                }831            })832        });833        return Promise.all(handlesPromises).then(function() {834            return self._rpc({835                    model: 'account.reconciliation.widget',836                    method: 'process_bank_statement_line',837                    args: [ids, values],838                    context: self.context,839                })840                .then(self._validatePostProcess.bind(self))841                .then(function () {842                    self.valuenow += handles.length;843                    return {handles: handles};844                });845        });846    },847    //--------------------------------------------------------------------------848    // Private849    //--------------------------------------------------------------------------850    /**851     * add a line proposition after checking receivable and payable accounts constraint852     *853     * @private854     * @param {Object} line855     * @param {Object} prop856     */857    _addProposition: function (line, prop) {858        line.reconciliation_proposition.push(prop);859    },860    /**861     * stop the editable proposition line and remove it if it's invalid then862     * compute the line863     *864     * See :func:`_computeLine`865     *866     * @private867     * @param {string} handle868     * @returns {Promise}869     */870    _blurProposition: function (handle) {871        var line = this.getLine(handle);872        line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (l) {873            l.__focus = false;874            return !l.invalid;875        });876    },877    /**878     * When changing partner, read property_account_receivable and payable879     * of that partner because the counterpart account might cahnge depending880     * on the partner881     *882     * @private883     * @param {string} handle884     * @param {integer} partner_id885     * @returns {Promise}886     */887    _changePartner: function (handle, partner_id) {888        var self = this;889        return this._rpc({890                model: 'res.partner',891                method: 'read',892                args: [partner_id, ["property_account_receivable_id", "property_account_payable_id"]],893            }).then(function (result) {894                if (result.length > 0) {895                    var line = self.getLine(handle);896                    self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0];897                }898            });899    },900    /**901     * Calculates the balance; format each proposition amount_str and mark as902     * invalid the line with empty account_id, amount or label903     * Check the taxes server side for each updated propositions with tax_ids904     * extended by ManualModel905     *906     * @private907     * @param {Object} line908     * @returns {Promise}909     */910    _computeLine: function (line) {911        //balance_type912        var self = this;913        // compute taxes914        var tax_defs = [];915        var reconciliation_proposition = [];916        var formatOptions = {917            currency_id: line.st_line.currency_id,918        };919        line.to_check = false;920        _.each(line.reconciliation_proposition, function (prop) {921            if (prop.to_check) {922                // If one of the proposition is to_check, set the global to_check flag to true923                line.to_check = true;924            }925            if (prop.tax_repartition_line_id) {926                if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) {927                    reconciliation_proposition.push(prop);928                }929                return;930            }931            if (!prop.already_paid && parseInt(prop.id)) {932                prop.is_move_line = true;933            }934            reconciliation_proposition.push(prop);935            if (prop.tax_ids && prop.tax_ids.length && prop.__tax_to_recompute && prop.base_amount) {936                reconciliation_proposition = _.filter(reconciliation_proposition, function (p) {937                    return !p.tax_repartition_line_id || p.link !== prop.id;938                });939                var args = [prop.tax_ids.map(function(el){return el.id;}), prop.base_amount, formatOptions.currency_id];940                var add_context = {'round': true};941                if(prop.tax_ids.length === 1 && line.createForm && line.createForm.force_tax_included)942                    add_context.force_price_include = true;943                tax_defs.push(self._rpc({944                        model: 'account.tax',945                        method: 'json_friendly_compute_all',946                        args: args,947                        context: $.extend({}, self.context || {}, add_context),948                    })949                    .then(function (result) {950                        _.each(result.taxes, function(tax){951                            var tax_prop = self._formatQuickCreate(line, {952                                'link': prop.id,953                                'tax_ids': tax.tax_ids,954                                'tax_repartition_line_id': tax.tax_repartition_line_id,955                                'tag_ids': tax.tag_ids,956                                'amount': tax.amount,957                                'label': prop.label ? prop.label + " " + tax.name : tax.name,958                                'date': prop.date,959                                'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id,960                                'analytic': tax.analytic,961                                '__focus': false962                            });963                            prop.tax_exigible = tax.tax_exigibility === 'on_payment' ? true : undefined;964                            prop.amount = tax.base;965                            prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions);966                            prop.invalid = !self._isValid(prop);967                            tax_prop.amount_str = field_utils.format.monetary(Math.abs(tax_prop.amount), {}, formatOptions);968                            tax_prop.invalid = prop.invalid;969                            reconciliation_proposition.push(tax_prop);970                        });971                        prop.tag_ids = result.base_tags;972                    }));973            } else {974                prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions);975                prop.display = self._isDisplayedProposition(prop);976                prop.invalid = !self._isValid(prop);977            }978        });979        return Promise.all(tax_defs).then(function () {980            _.each(reconciliation_proposition, function (prop) {981                prop.__tax_to_recompute = false;982            });983            line.reconciliation_proposition = reconciliation_proposition;984            var amount_currency = 0;985            var total = line.st_line.amount || 0;986            var isOtherCurrencyId = _.uniq(_.pluck(_.reject(reconciliation_proposition, 'invalid'), 'currency_id'));987            isOtherCurrencyId = isOtherCurrencyId.length === 1 && !total && isOtherCurrencyId[0] !== formatOptions.currency_id ? isOtherCurrencyId[0] : false;988            _.each(reconciliation_proposition, function (prop) {989                if (!prop.invalid) {990                    total -= prop.partial_amount || prop.amount;991                    if (isOtherCurrencyId) {992                        amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency);993                    }994                }995            });996            var company_currency = session.get_currency(line.st_line.currency_id);997            var company_precision = company_currency && company_currency.digits[1] || 2;998            total = utils.round_decimals(total, company_precision) || 0;999            if(isOtherCurrencyId){1000                var other_currency = session.get_currency(isOtherCurrencyId);1001                var other_precision = other_currency && other_currency.digits[1] || 2;1002                amount_currency = utils.round_decimals(amount_currency, other_precision);1003            }1004            line.balance = {1005                amount: total,1006                amount_str: field_utils.format.monetary(Math.abs(total), {}, formatOptions),1007                currency_id: isOtherCurrencyId,1008                amount_currency: isOtherCurrencyId ? amount_currency : total,1009                amount_currency_str: isOtherCurrencyId ? field_utils.format.monetary(Math.abs(amount_currency), {}, {1010                    currency_id: isOtherCurrencyId1011                }) : false,1012                account_code: self.accounts[line.st_line.open_balance_account_id],1013            };1014            line.balance.show_balance = line.balance.amount_currency != 0;1015            line.balance.type = line.balance.amount_currency ? (line.st_line.partner_id ? 0 : -1) : 1;1016        });1017    },1018    /**1019     *1020     *1021     * @private1022     * @param {string} handle1023     * @param {integer} reconcileModelId1024     */1025    _computeReconcileModels: function (handle, reconcileModelId) {1026        var line = this.getLine(handle);1027        // if quick create with 2 lines who use 100%, change the both values in same time1028        var props = _.filter(line.reconciliation_proposition, {'reconcileModelId': reconcileModelId, '__focus': true});1029        if (props.length === 2 && props[0].percent && props[1].percent) {1030            if (props[0].percent + props[1].percent === 100) {1031                props[0].base_amount = props[0].amount = line.st_line.amount - props[1].base_amount;1032                props[0].__tax_to_recompute = true;1033            }1034        }1035    },1036    /**1037     * format a name_get into an object {id, display_name}, idempotent1038     *1039     * @private1040     * @param {Object|Array} [value] data or name_get1041     */1042    _formatNameGet: function (value) {1043        return value ? (value.id ? value : {'id': value[0], 'display_name': value[1]}) : false;1044    },1045    _formatMany2ManyTags: function (value) {1046        var res = [];1047        for (var i=0, len=value.length; i<len; i++) {1048            res[i] = {'id': value[i][0], 'display_name': value[i][1]};1049        }1050        return res;1051    },1052    _formatMany2ManyTagsTax: function(value) {1053        var res = [];1054        for (var i=0; i<value.length; i++) {1055            res.push({id: value[i], display_name: this.taxes[value[i]] ? this.taxes[value[i]].display_name : ''});1056        }1057        return res;1058    },1059    /**1060     * Format each propositions (amount, label, account_id)1061     * extended in ManualModel1062     *1063     * @private1064     * @param {Object} line1065     * @param {Object[]} props1066     */1067    _formatLineProposition: function (line, props) {1068        var self = this;1069        if (props.length) {1070            _.each(props, function (prop) {1071                prop.amount = prop.debit || -prop.credit;1072                prop.label = prop.name;1073                prop.account_id = self._formatNameGet(prop.account_id || line.account_id);1074                prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str;1075                prop.to_check = !!prop.to_check;1076            });1077        }1078    },1079    /**1080     * Format each server lines and propositions and compute all lines1081     * overridden in ManualModel1082     *1083     * @see '_computeLine'1084     *1085     * @private1086     * @param {Object[]} lines1087     * @returns {Promise}1088     */1089    _formatLine: function (lines) {1090        var self = this;1091        var defs = [];1092        _.each(lines, function (data) {1093            var line = _.find(self.lines, function (l) {1094                return l.id === data.st_line.id;1095            });1096            line.visible = true;1097            line.limitMoveLines = self.limitMoveLines;1098            _.extend(line, data);1099            self._formatLineProposition(line, line.reconciliation_proposition);1100            if (!line.reconciliation_proposition.length) {1101                delete line.reconciliation_proposition;1102            }1103            // No partner set on st_line and all matching amls have the same one: set it on the st_line.1104            defs.push(1105                self._computeLine(line)1106                .then(function(){1107                    if(!line.st_line.partner_id && line.reconciliation_proposition.length > 0){1108                        var hasDifferentPartners = function(prop){1109                            return !prop.partner_id || prop.partner_id != line.reconciliation_proposition[0].partner_id;1110                        };1111                        if(!_.any(line.reconciliation_proposition, hasDifferentPartners)){1112                            return self.changePartner(line.handle, {1113                                'id': line.reconciliation_proposition[0].partner_id,1114                                'display_name': line.reconciliation_proposition[0].partner_name,1115                            }, true);1116                        }1117                    }else if(!line.st_line.partner_id && line.partner_id && line.partner_name){1118                        return self.changePartner(line.handle, {1119                            'id': line.partner_id,1120                            'display_name': line.partner_name,1121                        }, true);1122                    }1123                    return true;1124                })1125                .then(function(){1126                    return data.write_off ? self.quickCreateProposition(line.handle, data.model_id) : true;1127                })1128                .then(function() {1129                    // If still no partner set, take the one from context, if it exists1130                    if (!line.st_line.partner_id && self.context.partner_id && self.context.partner_name) {1131                        return self.changePartner(line.handle, {1132                            'id': self.context.partner_id,1133                            'display_name': self.context.partner_name,1134                        }, true);1135                    }1136                    return true;1137                })1138            );1139        });1140        return Promise.all(defs);1141    },1142    /**1143     * Format the server value then compute the line1144     * overridden in ManualModel1145     *1146     * @see '_computeLine'1147     *1148     * @private1149     * @param {string} handle1150     * @param {Object[]} mv_lines1151     * @returns {Promise}1152     */1153    _formatMoveLine: function (handle, mode, mv_lines) {1154        var self = this;1155        var line = this.getLine(handle);1156        line['mv_lines_'+mode] = _.uniq(line['mv_lines_'+mode].concat(mv_lines), l => l.id);1157        if (mv_lines[0]){1158            line['remaining_'+mode] = mv_lines[0].recs_count - mv_lines.length;1159        } else if (line['mv_lines_'+mode].lenght == 0) {1160            line['remaining_'+mode] = 0;1161        }1162        this._formatLineProposition(line, mv_lines);1163        if ((line.mode == 'match_other' || line.mode == "match_rp") && !line['mv_lines_'+mode].length && !line['filter_'+mode].length) {1164            line.mode = self._getDefaultMode(handle);1165            if (line.mode !== 'match_rp' && line.mode !== 'match_other' && line.mode !== 'inactive') {1166                return this._computeLine(line).then(function () {1167                    return self.createProposition(handle);1168                });1169            }1170        } else {1171            return this._computeLine(line);1172        }1173    },1174    /**1175     * overridden in ManualModel1176     */1177    _getDefaultMode: function(handle) {1178        var line = this.getLine(handle);1179        if (line.balance.amount === 01180            && (!line.st_line.mv_lines_match_rp || line.st_line.mv_lines_match_rp.length === 0)1181            && (!line.st_line.mv_lines_match_other || line.st_line.mv_lines_match_other.length === 0)) {1182            return 'inactive';1183        }1184        if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) {1185            return 'match_rp';1186        }1187        if (line.mv_lines_match_other && line.mv_lines_match_other.length) {1188            return 'match_other';1189        }1190        return 'create';1191    },1192    _getAvailableModes: function(handle) {1193        var line = this.getLine(handle);1194        var modes = []1195        if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) {1196            modes.push('match_rp')1197        }1198        if (line.mv_lines_match_other && line.mv_lines_match_other.length) {1199            modes.push('match_other')1200        }1201        modes.push('create')1202        return modes1203    },1204    /**1205     * Apply default values for the proposition, format datas and format the1206     * base_amount with the decimal number from the currency1207     * extended in ManualModel1208     *1209     * @private1210     * @param {Object} line1211     * @param {Object} values1212     * @returns {Object}1213     */1214    _formatQuickCreate: function (line, values) {1215        values = values || {};1216        var today = new moment().utc().format();1217        var account = this._formatNameGet(values.account_id);1218        var formatOptions = {1219            currency_id: line.st_line.currency_id,1220        };1221        var amount;1222        switch(values.amount_type) {1223            case 'percentage':1224                amount = line.balance.amount * values.amount / 100;1225                break;1226            case 'regex':1227                var matching = line.st_line.name.match(new RegExp(values.amount_from_label_regex))1228                amount = 0;1229                if (matching && matching.length == 2) {1230                    matching = matching[1].replace(new RegExp('\\D' + values.decimal_separator, 'g'), '');1231                    matching = matching.replace(values.decimal_separator, '.');1232                    amount = parseFloat(matching) || 0;1233                    amount = line.balance.amount > 0 ? amount : -amount;1234                }1235                break;1236            case 'fixed':1237                amount = values.amount;1238                break;1239            default:1240                amount = values.amount !== undefined ? values.amount : line.balance.amount;1241        }1242        var prop = {1243            'id': _.uniqueId('createLine'),1244            'label': values.label || line.st_line.name,1245            'account_id': account,1246            'account_code': account ? this.accounts[account.id] : '',1247            'analytic_account_id': this._formatNameGet(values.analytic_account_id),1248            'analytic_tag_ids': this._formatMany2ManyTags(values.analytic_tag_ids || []),1249            'journal_id': this._formatNameGet(values.journal_id),1250            'tax_ids': this._formatMany2ManyTagsTax(values.tax_ids || []),1251            'tag_ids': values.tag_ids,1252            'tax_repartition_line_id': values.tax_repartition_line_id,1253            'debit': 0,1254            'credit': 0,1255            'date': values.date ? values.date : field_utils.parse.date(today, {}, {isUTC: true}),1256            'force_tax_included': values.force_tax_included || false,1257            'base_amount': amount,1258            'percent': values.amount_type === "percentage" ? values.amount : null,1259            'link': values.link,1260            'display': true,1261            'invalid': true,1262            'to_check': !!values.to_check,1263            '__tax_to_recompute': true,1264            '__focus': '__focus' in values ? values.__focus : true,1265        };1266        if (prop.base_amount) {1267            // Call to format and parse needed to round the value to the currency precision1268            var sign = prop.base_amount < 0 ? -1 : 1;1269            var amount = field_utils.format.monetary(Math.abs(prop.base_amount), {}, formatOptions);1270            prop.base_amount = sign * field_utils.parse.monetary(amount, {}, formatOptions);1271        }1272        prop.amount = prop.base_amount;1273        return prop;1274    },1275    /**1276     * Return list of account_move_line that has been selected and needs to be removed1277     * from other calls.1278     *1279     * @private1280     * @returns {Array} list of excluded ids1281     */1282    _getExcludedIds: function () {1283        var excludedIds = [];1284        _.each(this.lines, function(line) {1285            if (line.reconciliation_proposition) {1286                _.each(line.reconciliation_proposition, function(prop) {1287                    if (parseInt(prop['id'])) {1288                        excludedIds.push(prop['id']);1289                    }1290                });1291            }1292        });1293        return excludedIds;1294    },1295    /**1296     * Defined whether the line is to be displayed or not. Here, we only display1297     * the line if it comes from the server or if an account is defined when it1298     * is created1299     * extended in ManualModel1300     *1301     * @private1302     * @param {object} prop1303     * @returns {Boolean}1304     */1305    _isDisplayedProposition: function (prop) {1306        return !isNaN(prop.id) || !!prop.account_id;1307    },1308    /**1309     * extended in ManualModel1310     * @private1311     * @param {object} prop1312     * @returns {Boolean}1313     */1314    _isValid: function (prop) {1315        return !isNaN(prop.id) || prop.account_id && prop.amount && prop.label && !!prop.label.length;1316    },1317    /**1318     * Fetch 'account.reconciliation.widget' propositions.1319     * overridden in ManualModel1320     *1321     * @see '_formatMoveLine'1322     *1323     * @private1324     * @param {string} handle1325     * @returns {Promise}1326     */1327    _performMoveLine: function (handle, mode, limit) {1328        limit = limit || this.limitMoveLines;1329        var line = this.getLine(handle);1330        var excluded_ids = _.map(_.union(line.reconciliation_proposition, line.mv_lines_match_rp, line.mv_lines_match_other), function (prop) {1331            return _.isNumber(prop.id) ? prop.id : null;1332        }).filter(id => id != null);1333        var filter = line['filter_'+mode] || "";1334        return this._rpc({1335                model: 'account.reconciliation.widget',1336                method: 'get_move_lines_for_bank_statement_line',1337                args: [line.id, line.st_line.partner_id, excluded_ids, filter, 0, limit, mode === 'match_rp' ? 'rp' : 'other'],1338                context: this.context,1339            })1340            .then(this._formatMoveLine.bind(this, handle, mode));1341    },1342    /**1343     * format the proposition to send information server side1344     * extended in ManualModel1345     *1346     * @private1347     * @param {object} line1348     * @param {object} prop1349     * @returns {object}1350     */1351    _formatToProcessReconciliation: function (line, prop) {1352        var amount = -prop.amount;1353        if (prop.partial_amount) {1354            amount = -prop.partial_amount;1355        }1356        var result = {1357            name : prop.label,1358            debit : amount > 0 ? amount : 0,1359            credit : amount < 0 ? -amount : 0,1360            tax_exigible: prop.tax_exigible,1361            analytic_tag_ids: [[6, null, _.pluck(prop.analytic_tag_ids, 'id')]]1362        };1363        if (!isNaN(prop.id)) {1364            result.counterpart_aml_id = prop.id;1365        } else {1366            result.account_id = prop.account_id.id;1367            if (prop.journal_id) {1368                result.journal_id = prop.journal_id.id;1369            }1370        }1371        if (!isNaN(prop.id)) result.counterpart_aml_id = prop.id;1372        if (prop.analytic_account_id) result.analytic_account_id = prop.analytic_account_id.id;1373        if (prop.tax_ids && prop.tax_ids.length) result.tax_ids = [[6, null, _.pluck(prop.tax_ids, 'id')]];1374        if (prop.tag_ids && prop.tag_ids.length) result.tag_ids = [[6, null, prop.tag_ids]];1375        if (prop.tax_repartition_line_id) result.tax_repartition_line_id = prop.tax_repartition_line_id;1376        if (prop.reconcileModelId) result.reconcile_model_id = prop.reconcileModelId1377        return result;1378    },1379    /**1380     * Hook to handle return values of the validate's line process.1381     *1382     * @private1383     * @param {Object} data1384     * @param {Object[]} data.moves list of processed account.move1385     * @returns {Deferred}1386     */1387    _validatePostProcess: function (data) {1388        var self = this;1389        return Promise.resolve();1390    },1391});1392/**1393 * Model use to fetch, format and update 'account.move.line' and 'res.partner'1394 * datas allowing manual reconciliation1395 */1396var ManualModel = StatementModel.extend({1397    quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'date', 'to_check'],1398    modes: ['create', 'match'],1399    //--------------------------------------------------------------------------1400    // Public1401    //--------------------------------------------------------------------------1402    /**1403     * Return a boolean telling if load button needs to be displayed or not1404     *1405     * @returns {boolean} true if load more button needs to be displayed1406     */1407    hasMoreLines: function () {1408        if (this.manualLines.length > this.pagerIndex) {1409            return true;1410        }1411        return false;1412    },1413    /**1414     * load data from1415     * - 'account.reconciliation.widget' fetch the lines to reconciliate1416     * - 'account.account' fetch all account code1417     *1418     * @param {Object} context1419     * @param {string} [context.mode] 'customers', 'suppliers' or 'accounts'1420     * @param {integer[]} [context.company_ids]1421     * @param {integer[]} [context.partner_ids] used for 'customers' and1422     *   'suppliers' mode1423     * @returns {Promise}1424     */1425    load: function (context) {1426        var self = this;1427        this.context = context;1428        var domain_account_id = [];1429        if (context && context.company_ids) {1430            domain_account_id.push(['company_id', 'in', context.company_ids]);1431        }1432        var def_account = this._rpc({1433                model: 'account.account',1434                method: 'search_read',1435                domain: domain_account_id,1436                fields: ['code'],1437            })1438            .then(function (accounts) {1439                self.account_ids = _.pluck(accounts, 'id');1440                self.accounts = _.object(self.account_ids, _.pluck(accounts, 'code'));1441            });1442        var domainReconcile = [];1443        var session_allowed_company_ids = session.user_context.allowed_company_ids || []1444        var company_ids = context && context.company_ids || session_allowed_company_ids.slice(0, 1);1445        if (company_ids) {1446            domainReconcile.push(['company_id', 'in', company_ids]);1447        }1448        var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile});1449        var def_taxes = this._loadTaxes();1450        return Promise.all([def_reconcileModel, def_account, def_taxes]).then(function () {1451            switch(context.mode) {1452                case 'customers':1453                case 'suppliers':1454                    var mode = context.mode === 'customers' ? 'receivable' : 'payable';1455                    var args = ['partner', context.partner_ids || null, mode];1456                    return self._rpc({1457                            model: 'account.reconciliation.widget',1458                            method: 'get_data_for_manual_reconciliation',1459                            args: args,1460                            context: context,1461                        })1462                        .then(function (result) {1463                            self.manualLines = result;1464                            self.valuenow = 0;1465                            self.valuemax = Object.keys(self.manualLines).length;1466                            var lines = self.manualLines.slice(0, self.defaultDisplayQty);1467                            self.pagerIndex = lines.length;1468                            return self.loadData(lines);1469                        });1470                case 'accounts':1471                    return self._rpc({1472                            model: 'account.reconciliation.widget',1473                            method: 'get_data_for_manual_reconciliation',1474                            args: ['account', context.account_ids || self.account_ids],1475                            context: context,1476                        })1477                        .then(function (result) {1478                            self.manualLines = result;1479                            self.valuenow = 0;1480                            self.valuemax = Object.keys(self.manualLines).length;1481                            var lines = self.manualLines.slice(0, self.defaultDisplayQty);1482                            self.pagerIndex = lines.length;1483                            return self.loadData(lines);1484                        });1485                default:1486                    var partner_ids = context.partner_ids || null;1487                    var account_ids = context.account_ids || self.account_ids || null;1488                    return self._rpc({1489                            model: 'account.reconciliation.widget',1490                            method: 'get_all_data_for_manual_reconciliation',1491                            args: [partner_ids, account_ids],1492                            context: context,1493                        })1494                        .then(function (result) {1495                            // Flatten the result1496                            self.manualLines = [].concat(result.accounts, result.customers, result.suppliers);1497                            self.valuenow = 0;1498                            self.valuemax = Object.keys(self.manualLines).length;1499                            var lines = self.manualLines.slice(0, self.defaultDisplayQty);1500                            self.pagerIndex = lines.length;1501                            return self.loadData(lines);1502                        });1503            }1504        });1505    },1506    /**1507     * Reload data by calling load1508     * It overrides super.reload() because1509     * it is not adapted for this model.1510     *1511     * Use case: coming back to manual reconcilation1512     *           in breadcrumb1513     */1514    reload: function () {1515        this.lines = {};1516        return this.load(this.context);1517    },1518    /**1519     * Load more partners/accounts1520     * overridden in ManualModel1521     *1522     * @param {integer} qty quantity to load1523     * @returns {Promise}1524     */1525    loadMore: function(qty) {1526        if (qty === undefined) {1527            qty = this.defaultDisplayQty;1528        }1529        var lines = this.manualLines.slice(this.pagerIndex, this.pagerIndex + qty);1530        this.pagerIndex += qty;1531        return this.loadData(lines);1532    },1533    /**1534     * Method to load informations on lines1535     *1536     * @param {Array} lines manualLines to load1537     * @returns {Promise}1538     */1539    loadData: function(lines) {1540        var self = this;1541        var defs = [];1542        _.each(lines, function (l) {1543            defs.push(self._formatLine(l.mode, l));1544        });1545        return Promise.all(defs);1546    },1547    /**1548     * Mark the account or the partner as reconciled1549     *1550     * @param {(string|string[])} handle1551     * @returns {Promise<Array>} resolved with the handle array1552     */1553    validate: function (handle) {1554        var self = this;1555        var handles = [];1556        if (handle) {1557            handles = [handle];1558        } else {1559            _.each(this.lines, function (line, handle) {1560                if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) {1561                    handles.push(handle);1562                }1563            });1564        }1565        var def = Promise.resolve();1566        var process_reconciliations = [];1567        var reconciled = [];1568        _.each(handles, function (handle) {1569            var line = self.getLine(handle);1570            if(line.reconciled) {1571                return;1572            }1573            var props = line.reconciliation_proposition;1574            if (!props.length) {1575                self.valuenow++;1576                reconciled.push(handle);1577                line.reconciled = true;1578                process_reconciliations.push({1579                    id: line.type === 'accounts' ? line.account_id : line.partner_id,1580                    type: line.type,1581                    mv_line_ids: [],1582                    new_mv_line_dicts: [],1583                });1584            } else {1585                var mv_line_ids = _.pluck(_.filter(props, function (prop) {return !isNaN(prop.id);}), 'id');1586                var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id) && prop.display;}), self._formatToProcessReconciliation.bind(self, line));1587                process_reconciliations.push({1588                    id: null,1589                    type: null,1590                    mv_line_ids: mv_line_ids,1591                    new_mv_line_dicts: new_mv_line_dicts1592                });1593            }1594            line.reconciliation_proposition = [];1595        });1596        if (process_reconciliations.length) {1597            def = self._rpc({1598                    model: 'account.reconciliation.widget',1599                    method: 'process_move_lines',1600                    args: [process_reconciliations],1601                });1602        }1603        return def.then(function() {1604            var defs = [];1605            var account_ids = [];1606            var partner_ids = [];1607            _.each(handles, function (handle) {1608                var line = self.getLine(handle);1609                if (line.reconciled) {1610                    return;1611                }1612                line.filter_match = "";1613                defs.push(self._performMoveLine(handle, 'match').then(function () {1614                    if(!line.mv_lines_match.length) {1615                        self.valuenow++;1616                        reconciled.push(handle);1617                        line.reconciled = true;1618                        if (line.type === 'accounts') {1619                            account_ids.push(line.account_id.id);1620                        } else {1621                            partner_ids.push(line.partner_id);1622                        }1623                    }1624                }));1625            });1626            return Promise.all(defs).then(function () {1627                if (partner_ids.length) {1628                    self._rpc({1629                            model: 'res.partner',1630                            method: 'mark_as_reconciled',1631                            args: [partner_ids],1632                        });1633                }1634                return {reconciled: reconciled, updated: _.difference(handles, reconciled)};1635            });1636        });1637    },1638    removeProposition: function (handle, id) {1639        var self = this;1640        var line = this.getLine(handle);1641        var defs = [];1642        var prop = _.find(line.reconciliation_proposition, {'id' : id});1643        if (prop) {1644            line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) {1645                return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link);1646            });1647            line.mv_lines_match = line.mv_lines_match || [];1648            line.mv_lines_match.unshift(prop);1649            // No proposition left and then, reset the st_line partner.1650            if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner)1651                defs.push(self.changePartner(line.handle));1652        }1653        line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match';1654        defs.push(this._computeLine(line));1655        return Promise.all(defs).then(function() {1656            return self.changeMode(handle, line.mode, true);1657        })1658    },1659    //--------------------------------------------------------------------------1660    // Private1661    //--------------------------------------------------------------------------1662    /**1663     * override change the balance type to display or not the reconcile button1664     *1665     * @override1666     * @private1667     * @param {Object} line1668     * @returns {Promise}1669     */1670    _computeLine: function (line) {1671        return this._super(line).then(function () {1672            var props = _.reject(line.reconciliation_proposition, 'invalid');1673            _.each(line.reconciliation_proposition, function(p) {1674                delete p.is_move_line;1675            });1676            line.balance.type = -1;1677            if (!line.balance.amount_currency && props.length) {1678                line.balance.type = 1;1679            } else if(_.any(props, function (prop) {return prop.amount > 0;}) &&1680                     _.any(props, function (prop) {return prop.amount < 0;})) {1681                line.balance.type = 0;1682            }1683        });1684    },1685    /**1686     * Format each server lines and propositions and compute all lines1687     *1688     * @see '_computeLine'1689     *1690     * @private1691     * @param {'customers' | 'suppliers' | 'accounts'} type1692     * @param {Object} data1693     * @returns {Promise}1694     */1695    _formatLine: function (type, data) {1696        var line = this.lines[_.uniqueId('rline')] = _.extend(data, {1697            type: type,1698            reconciled: false,1699            mode: 'inactive',1700            limitMoveLines: this.limitMoveLines,1701            filter_match: "",1702            reconcileModels: this.reconcileModels,1703            account_id: this._formatNameGet([data.account_id, data.account_name]),1704            st_line: data,1705            visible: true1706        });1707        this._formatLineProposition(line, line.reconciliation_proposition);1708        if (!line.reconciliation_proposition.length) {1709            delete line.reconciliation_proposition;1710        }1711        return this._computeLine(line);1712    },1713    /**1714     * override to add journal_id1715     *1716     * @override1717     * @private1718     * @param {Object} line1719     * @param {Object} props1720     */1721    _formatLineProposition: function (line, props) {1722        var self = this;1723        this._super(line, props);1724        if (props.length) {1725            _.each(props, function (prop) {1726                var tmp_value = prop.debit || prop.credit;1727                prop.credit = prop.credit !== 0 ? 0 : tmp_value;1728                prop.debit = prop.debit !== 0 ? 0 : tmp_value;1729                prop.amount = -prop.amount;1730                prop.journal_id = self._formatNameGet(prop.journal_id || line.journal_id);1731                prop.to_check = !!prop.to_check;1732            });1733        }1734    },1735    /**1736     * override to add journal_id on tax_created_line1737     *1738     * @private1739     * @param {Object} line1740     * @param {Object} values1741     * @returns {Object}1742     */1743    _formatQuickCreate: function (line, values) {1744        // Add journal to created line1745        if (values && values.journal_id === undefined && line && line.createForm && line.createForm.journal_id) {1746            values.journal_id = line.createForm.journal_id;1747        }1748        return this._super(line, values);1749    },1750    /**1751     * @override1752     * @param {object} prop1753     * @returns {Boolean}1754     */1755    _isDisplayedProposition: function (prop) {1756        return !!prop.journal_id && this._super(prop);1757    },1758    /**1759     * @override1760     * @param {object} prop1761     * @returns {Boolean}1762     */1763    _isValid: function (prop) {1764        return prop.journal_id && this._super(prop);1765    },1766    /**1767     * Fetch 'account.move.line' propositions.1768     *1769     * @see '_formatMoveLine'1770     *1771     * @override1772     * @private1773     * @param {string} handle1774     * @returns {Promise}1775     */1776    _performMoveLine: function (handle, mode, limit) {1777        limit = limit || this.limitMoveLines;1778        var line = this.getLine(handle);1779        var excluded_ids = _.map(_.union(line.reconciliation_proposition, line.mv_lines_match), function (prop) {1780            return _.isNumber(prop.id) ? prop.id : null;1781        }).filter(id => id != null);1782        var filter = line.filter_match || "";1783        var args = [line.account_id.id, line.partner_id, excluded_ids, filter, 0, limit];1784        return this._rpc({1785                model: 'account.reconciliation.widget',1786                method: 'get_move_lines_for_manual_reconciliation',1787                args: args,1788                context: this.context,1789            })1790            .then(this._formatMoveLine.bind(this, handle, ''));1791    },1792    _formatToProcessReconciliation: function (line, prop) {1793        var result = this._super(line, prop);1794        result['date'] = prop.date;1795        return result;1796    },1797    _getDefaultMode: function(handle) {1798        var line = this.getLine(handle);1799        if (line.balance.amount === 0 && (!line.st_line.mv_lines_match || line.st_line.mv_lines_match.length === 0)) {1800            return 'inactive';1801        }1802        return line.mv_lines_match.length > 0 ? 'match' : 'create';1803    },1804    _formatMoveLine: function (handle, mode, mv_lines) {1805        var self = this;1806        var line = this.getLine(handle);1807        line.mv_lines_match = _.uniq((line.mv_lines_match || []).concat(mv_lines), l => l.id);1808        this._formatLineProposition(line, mv_lines);1809        if (line.mode !== 'create' && !line.mv_lines_match.length && !line.filter_match.length) {1810            line.mode = this.avoidCreate || !line.balance.amount ? 'inactive' : 'create';1811            if (line.mode === 'create') {1812                return this._computeLine(line).then(function () {1813                    return self.createProposition(handle);1814                });1815            }1816        } else {1817            return this._computeLine(line);1818        }1819    },1820});1821return {1822    StatementModel: StatementModel,1823    ManualModel: ManualModel,1824};...

Full Screen

Full Screen

reconciliation_model.js

Source:reconciliation_model.js Github

copy

Full Screen

1odoo.define('account.ReconciliationModel', function (require) {2"use strict";3var BasicModel = require('web.BasicModel');4var field_utils = require('web.field_utils');5var utils = require('web.utils');6var session = require('web.session');7var WarningDialog = require('web.CrashManager').WarningDialog;8var core = require('web.core');9var _t = core._t;10/**11 * Model use to fetch, format and update 'account.reconciliation.widget',12 * datas allowing reconciliation13 *14 * The statement internal structure::15 *16 *  {17 *      valuenow: integer18 *      valuenow: valuemax19 *      [bank_statement_line_id]: {20 *          id: integer21 *          display_name: string22 *      }23 *      reconcileModels: [object]24 *      accounts: {id: code}25 *  }26 *27 * The internal structure of each line is::28 *29 *   {30 *      balance: {31 *          type: number - show/hide action button32 *          amount: number - real amount33 *          amount_str: string - formated amount34 *          account_code: string35 *      },36 *      st_line: {37 *          partner_id: integer38 *          partner_name: string39 *      }40 *      mode: string ('inactive', 'match_rp', 'match_other', 'create')41 *      reconciliation_proposition: {42 *          id: number|string43 *          partial_amount: number44 *          invalid: boolean - through the invalid line (without account, label...)45 *          account_code: string46 *          date: string47 *          date_maturity: string48 *          label: string49 *          amount: number - real amount50 *          amount_str: string - formated amount51 *          [already_paid]: boolean52 *          [partner_id]: integer53 *          [partner_name]: string54 *          [account_code]: string55 *          [journal_id]: {56 *              id: integer57 *              display_name: string58 *          }59 *          [ref]: string60 *          [is_partially_reconciled]: boolean61 *          [to_check]: boolean62 *          [amount_currency_str]: string|false (amount in record currency)63 *      }64 *      mv_lines_match_rp: object - idem than reconciliation_proposition65 *      mv_lines_match_other: object - idem than reconciliation_proposition66 *      limitMoveLines: integer67 *      filter: string68 *      [createForm]: {69 *          account_id: {70 *              id: integer71 *              display_name: string72 *          }73 *          tax_ids: {74 *              id: integer75 *              display_name: string76 *          }77 *          analytic_account_id: {78 *              id: integer79 *              display_name: string80 *          }81 *          analytic_tag_ids: {82 *          }83 *          label: string84 *          amount: number,85 *          [journal_id]: {86 *              id: integer87 *              display_name: string88 *          }89 *      }90 *   }91 */92var StatementModel = BasicModel.extend({93    avoidCreate: false,94    quickCreateFields: ['account_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'to_check'],95    // overridden in ManualModel96    modes: ['create', 'match_rp', 'match_other'],97    /**98     * @override99     *100     * @param {Widget} parent101     * @param {object} options102     */103    init: function (parent, options) {104        this._super.apply(this, arguments);105        this.reconcileModels = [];106        this.lines = {};107        this.valuenow = 0;108        this.valuemax = 0;109        this.alreadyDisplayed = [];110        this.domain = [];111        this.defaultDisplayQty = options && options.defaultDisplayQty || 10;112        this.limitMoveLines = options && options.limitMoveLines || 15;113        this.display_context = 'init';114    },115    //--------------------------------------------------------------------------116    // Public117    //--------------------------------------------------------------------------118    /**119     * add a reconciliation proposition from the matched lines120     * We also display a warning if the user tries to add 2 line with different121     * account type122     *123     * @param {string} handle124     * @param {number} mv_line_id125     * @returns {Promise}126     */127    addProposition: function (handle, mv_line_id) {128        var self = this;129        var line = this.getLine(handle);130        var prop = _.clone(_.find(line['mv_lines_'+line.mode], {'id': mv_line_id}));131        this._addProposition(line, prop);132        line['mv_lines_'+line.mode] = _.filter(line['mv_lines_'+line.mode], l => l['id'] != mv_line_id);133        // remove all non valid lines134        line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (prop) {return prop && !prop.invalid;});135        // Onchange the partner if not already set on the statement line.136        if(!line.st_line.partner_id && line.reconciliation_proposition137            && line.reconciliation_proposition.length == 1 && prop.partner_id && line.type === undefined){138            return this.changePartner(handle, {'id': prop.partner_id, 'display_name': prop.partner_name}, true);139        }140        return Promise.all([141            this._computeLine(line),142            this._performMoveLine(handle, 'match_rp', line.mode == 'match_rp'? 1 : 0),143            this._performMoveLine(handle, 'match_other', line.mode == 'match_other'? 1 : 0)144        ]);145    },146    /**147     * change the filter for the target line and fetch the new matched lines148     *149     * @param {string} handle150     * @param {string} filter151     * @returns {Promise}152     */153    changeFilter: function (handle, filter) {154        var line = this.getLine(handle);155        line['filter_'+line.mode] = filter;156        line['mv_lines_'+line.mode] = [];157        return this._performMoveLine(handle, line.mode);158    },159    /**160     * change the mode line ('inactive', 'match_rp', 'match_other', 'create'),161     * and fetch the new matched lines or prepare to create a new line162     *163     * ``match_rp``164     *   display the matched lines from receivable/payable accounts, the user165     *   can select the lines to apply there as proposition166     * ``match_other``167     *   display the other matched lines, the user can select the lines to apply168     *   there as proposition169     * ``create``170     *   display fields and quick create button to create a new proposition171     *   for the reconciliation172     *173     * @param {string} handle174     * @param {'inactive' | 'match_rp' | 'create'} mode175     * @returns {Promise}176     */177    changeMode: function (handle, mode) {178        var self = this;179        var line = this.getLine(handle);180        if (mode === 'default') {181            var match_requests = self.modes.filter(x => x.startsWith('match')).map(x => this._performMoveLine(handle, x))182            return Promise.all(match_requests).then(function() {183                return self.changeMode(handle, self._getDefaultMode(handle));184            });185        }186        if (mode === 'next') {187            var available_modes = self._getAvailableModes(handle)188            mode = available_modes[(available_modes.indexOf(line.mode) + 1) % available_modes.length];189        }190        line.mode = mode;191        if (['match_rp', 'match_other'].includes(line.mode)) {192            if (!(line['mv_lines_' + line.mode] && line['mv_lines_' + line.mode].length)) {193                return this._performMoveLine(handle, line.mode);194            } else {195                return this._formatMoveLine(handle, line.mode, []);196            }197        }198        if (line.mode === 'create') {199            return this.createProposition(handle);200        }201        return Promise.resolve();202    },203    /**204     * fetch the more matched lines205     *206     * @param {string} handle207     * @returns {Promise}208     */209    changeOffset: function (handle) {210        var line = this.getLine(handle);211        return this._performMoveLine(handle, line.mode);212    },213    /**214     * change the partner on the line and fetch the new matched lines215     *216     * @param {string} handle217     * @param {bool} preserveMode218     * @param {Object} partner219     * @param {string} partner.display_name220     * @param {number} partner.id221     * @returns {Promise}222     */223    changePartner: function (handle, partner, preserveMode) {224        var self = this;225        var line = this.getLine(handle);226        line.st_line.partner_id = partner && partner.id;227        line.st_line.partner_name = partner && partner.display_name || '';228        line.mv_lines_match_rp = [];229        line.mv_lines_match_other = [];230        return Promise.resolve(partner && this._changePartner(handle, partner.id))231                .then(function() {232                    if(line.st_line.partner_id){233                        _.each(line.reconciliation_proposition, function(prop){234                            if(prop.partner_id != line.st_line.partner_id){235                                line.reconciliation_proposition = [];236                                return false;237                            }238                        });239                    }240                    return self._computeLine(line);241                })242                .then(function () {243                    return self.changeMode(handle, preserveMode ? line.mode : 'default', true);244                })245    },246    /**247     * close the statement248     * @returns {Promise<number>} resolves to the res_id of the closed statements249     */250    closeStatement: function () {251        var self = this;252        return this._rpc({253                model: 'account.bank.statement.line',254                method: 'button_confirm_bank',255                args: [self.bank_statement_line_id.id],256            })257            .then(function () {258                return self.bank_statement_line_id.id;259            });260    },261    /**262     *263     * then open the first available line264     *265     * @param {string} handle266     * @returns {Promise}267     */268    createProposition: function (handle) {269        var line = this.getLine(handle);270        var prop = _.filter(line.reconciliation_proposition, '__focus');271        prop = this._formatQuickCreate(line);272        line.reconciliation_proposition.push(prop);273        line.createForm = _.pick(prop, this.quickCreateFields);274        return this._computeLine(line);275    },276    /**277     * Return context information and journal_id278     * @returns {Object} context279     */280    getContext: function () {281        return this.context;282    },283    /**284     * Return the lines that needs to be displayed by the widget285     *286     * @returns {Object} lines that are loaded and not yet displayed287     */288    getStatementLines: function () {289        var self = this;290        var linesToDisplay = _.pick(this.lines, function(value, key, object) {291            if (value.visible === true && self.alreadyDisplayed.indexOf(key) === -1) {292                self.alreadyDisplayed.push(key);293                return object;294            }295        });296        return linesToDisplay;297    },298    /**299     * Return a boolean telling if load button needs to be displayed or not300     * overridden in ManualModel301     *302     * @returns {boolean} true if load more button needs to be displayed303     */304    hasMoreLines: function () {305        var notDisplayed = _.filter(this.lines, function(line) { return !line.visible; });306        if (notDisplayed.length > 0) {307            return true;308        }309        return false;310    },311    /**312     * get the line data for this handle313     *314     * @param {Object} handle315     * @returns {Object}316     */317    getLine: function (handle) {318        return this.lines[handle];319    },320    /**321     * load data from322     *323     * - 'account.bank.statement' fetch the line id and bank_statement_id info324     * - 'account.reconcile.model'  fetch all reconcile model (for quick add)325     * - 'account.account' fetch all account code326     * - 'account.reconciliation.widget' fetch each line data327     *328     * overridden in ManualModel329     * @param {Object} context330     * @param {number[]} context.statement_line_ids331     * @returns {Promise}332     */333    load: function (context) {334        var self = this;335        this.context = context;336        this.statement_line_ids = context.statement_line_ids;337        if (this.statement_line_ids === undefined) {338            // This could be undefined if the user pressed F5, take everything as fallback instead of rainbowman339            return self._rpc({340                model: 'account.bank.statement.line',341                method: 'search_read',342                fields: ['id'],343                domain: [['journal_id', '=?', context.active_id]],344            }).then(function (result) {345                self.statement_line_ids = result.map(r => r.id);346                return self.reload()347            })348        } else {349            return self.reload();350        }351    },352    /**353     * Load more bank statement line354     *355     * @param {integer} qty quantity to load356     * @returns {Promise}357     */358    loadMore: function(qty) {359        if (qty === undefined) {360            qty = this.defaultDisplayQty;361        }362        var ids = _.pluck(this.lines, 'id');363        ids = ids.splice(this.pagerIndex, qty);364        this.pagerIndex += qty;365        return this.loadData(ids, this._getExcludedIds());366    },367    /**368     * RPC method to load informations on lines369     * overridden in ManualModel370     *371     * @param {Array} ids ids of bank statement line passed to rpc call372     * @param {Array} excluded_ids list of move_line ids that needs to be excluded from search373     * @returns {Promise}374     */375    loadData: function(ids) {376        var self = this;377        var excluded_ids = this._getExcludedIds();378        return self._rpc({379            model: 'account.reconciliation.widget',380            method: 'get_bank_statement_line_data',381            args: [ids, excluded_ids],382            context: self.context,383        })384        .then(self._formatLine.bind(self));385    },386    /**387     * Reload all data388     */389    reload: function() {390        var self = this;391        self.alreadyDisplayed = [];392        self.lines = {};393        self.pagerIndex = 0;394        var def_statement = this._rpc({395                model: 'account.reconciliation.widget',396                method: 'get_bank_statement_data',397                kwargs: {"bank_statement_line_ids":self.statement_line_ids, "srch_domain":self.domain},398                context: self.context,399            })400            .then(function (statement) {401                self.statement = statement;402                self.bank_statement_line_id = self.statement_line_ids.length === 1 ? {id: self.statement_line_ids[0], display_name: statement.statement_name} : false;403                self.valuenow = self.valuenow || statement.value_min;404                self.valuemax = self.valuemax || statement.value_max;405                self.context.journal_id = statement.journal_id;406                _.each(statement.lines, function (res) {407                    var handle = _.uniqueId('rline');408                    self.lines[handle] = {409                        id: res.st_line.id,410                        partner_id: res.st_line.partner_id,411                        handle: handle,412                        reconciled: false,413                        mode: 'inactive',414                        mv_lines_match_rp: [],415                        mv_lines_match_other: [],416                        filter_match_rp: "",417                        filter_match_other: "",418                        reconciliation_proposition: [],419                        reconcileModels: [],420                    };421                });422            });423        var domainReconcile = [];424        if (self.context && self.context.company_ids) {425            domainReconcile.push(['company_id', 'in', self.context.company_ids]);426        }427        if (self.context && self.context.active_model === 'account.journal' && self.context.active_ids) {428            domainReconcile.push('|');429            domainReconcile.push(['match_journal_ids', '=', false]);430            domainReconcile.push(['match_journal_ids', 'in', self.context.active_ids]);431        }432        var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile});433        var def_account = this._rpc({434                model: 'account.account',435                method: 'search_read',436                fields: ['code'],437            })438            .then(function (accounts) {439                self.accounts = _.object(_.pluck(accounts, 'id'), _.pluck(accounts, 'code'));440            });441        var def_taxes = self._loadTaxes();442        return Promise.all([def_statement, def_reconcileModel, def_account, def_taxes]).then(function () {443            _.each(self.lines, function (line) {444                line.reconcileModels = self.reconcileModels;445            });446            var ids = _.pluck(self.lines, 'id');447            ids = ids.splice(0, self.defaultDisplayQty);448            self.pagerIndex = ids.length;449            return self._formatLine(self.statement.lines);450        });451    },452    _readAnalyticTags: function (params) {453        var self = this;454        this.analyticTags = {};455        if (!params || !params.res_ids || !params.res_ids.length) {456            return $.when();457        }458        var fields = (params && params.fields || []).concat(['id', 'display_name']);459        return this._rpc({460                model: 'account.analytic.tag',461                method: 'read',462                args: [463                    params.res_ids,464                    fields,465                ],466            }).then(function (tags) {467                for (var i=0; i<tags.length; i++) {468                    var tag = tags[i];469                    self.analyticTags[tag.id] = tag;470                }471            });472    },473    _loadReconciliationModel: function (params) {474        var self = this;475        return this._rpc({476                model: 'account.reconcile.model',477                method: 'search_read',478                domain: params.domainReconcile || [],479            })480            .then(function (reconcileModels) {481               var analyticTagIds = [];482                for (var i=0; i<reconcileModels.length; i++) {483                    var modelTags = reconcileModels[i].analytic_tag_ids || [];484                    for (var j=0; j<modelTags.length; j++) {485                        if (analyticTagIds.indexOf(modelTags[j]) === -1) {486                            analyticTagIds.push(modelTags[j]);487                        }488                    }489                }490                return self._readAnalyticTags({res_ids: analyticTagIds}).then(function () {491                    for (var i=0; i<reconcileModels.length; i++) {492                        var recModel = reconcileModels[i];493                        var analyticTagData = [];494                        var modelTags = reconcileModels[i].analytic_tag_ids || [];495                        for (var j=0; j<modelTags.length; j++) {496                            var tagId = modelTags[j];497                            analyticTagData.push([tagId, self.analyticTags[tagId].display_name])498                        }499                        recModel.analytic_tag_ids = analyticTagData;500                    }501                    self.reconcileModels = reconcileModels;502                });503            });504    },505    _loadTaxes: function(){506        var self = this;507        self.taxes = {};508        return this._rpc({509                model: 'account.tax',510                method: 'search_read',511                fields: ['price_include', 'name'],512            }).then(function (taxes) {513                _.each(taxes, function(tax){514                    self.taxes[tax.id] = {515                        price_include: tax.price_include,516                        display_name: tax.name,517                    };518                });519                return taxes;520            });521    },522    /**523     * Add lines into the propositions from the reconcile model524     * Can add 2 lines, and each with its taxes. The second line become editable525     * in the create mode.526     *527     * @see 'updateProposition' method for more informations about the528     * 'amount_type'529     *530     * @param {string} handle531     * @param {integer} reconcileModelId532     * @returns {Promise}533     */534    quickCreateProposition: function (handle, reconcileModelId) {535        var self = this;536        var line = this.getLine(handle);537        var reconcileModel = _.find(this.reconcileModels, function (r) {return r.id === reconcileModelId;});538        var fields = ['account_id', 'amount', 'amount_type', 'analytic_account_id', 'journal_id', 'label', 'force_tax_included', 'tax_ids', 'analytic_tag_ids', 'to_check', 'amount_from_label_regex', 'decimal_separator'];539        this._blurProposition(handle);540        var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields));541        focus.reconcileModelId = reconcileModelId;542        line.reconciliation_proposition.push(focus);543        var defs = [];544        if (reconcileModel.has_second_line) {545            defs.push(self._computeLine(line).then(function() {546                var second = {};547                _.each(fields, function (key) {548                    second[key] = ("second_"+key) in reconcileModel ? reconcileModel["second_"+key] : reconcileModel[key];549                });550                var second_focus = self._formatQuickCreate(line, second);551                second_focus.reconcileModelId = reconcileModelId;552                line.reconciliation_proposition.push(second_focus);553                self._computeReconcileModels(handle, reconcileModelId);554            }))555        }556        return Promise.all(defs).then(function() {557            line.createForm = _.pick(focus, self.quickCreateFields);558            return self._computeLine(line);559        })560    },561    /**562     * Remove a proposition and switch to an active mode ('create' or 'match_rp' or 'match_other')563     * overridden in ManualModel564     *565     * @param {string} handle566     * @param {number} id (move line id)567     * @returns {Promise}568     */569    removeProposition: function (handle, id) {570        var self = this;571        var line = this.getLine(handle);572        var defs = [];573        var prop = _.find(line.reconciliation_proposition, {'id' : id});574        if (prop) {575            line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) {576                return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link);577            });578            if (prop['reconcileModelId'] === undefined) {579                if (['receivable', 'payable', 'liquidity'].includes(prop.account_type)) {580                    line.mv_lines_match_rp.unshift(prop);581                } else {582                    line.mv_lines_match_other.unshift(prop);583                }584            }585            // No proposition left and then, reset the st_line partner.586            if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner)587                defs.push(self.changePartner(line.handle));588        }589        line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match_rp';590        defs.push(this._computeLine(line));591        return Promise.all(defs).then(function() {592            return self.changeMode(handle, line.mode, true);593        })594    },595    getPartialReconcileAmount: function(handle, data) {596        var line = this.getLine(handle);597        var formatOptions = {598            currency_id: line.st_line.currency_id,599            noSymbol: true,600        };601        var prop = _.find(line.reconciliation_proposition, {'id': data.data});602        if (prop) {603            var amount = prop.partial_amount || prop.amount;604            // Check if we can get a partial amount that would directly set balance to zero605            var partial = Math.abs(line.balance.amount + amount);606            if (Math.abs(line.balance.amount) >= Math.abs(amount)) {607                amount = Math.abs(amount);608            } else if (partial <= Math.abs(prop.amount) && partial >= 0) {609                amount = partial;610            } else {611                amount = Math.abs(amount);612            }613            return field_utils.format.monetary(amount, {}, formatOptions);614        }615    },616    /**617     * Force the partial reconciliation to display the reconciliate button.618     *619     * @param {string} handle620     * @returns {Promise}621     */622    partialReconcile: function(handle, data) {623        var line = this.getLine(handle);624        var prop = _.find(line.reconciliation_proposition, {'id' : data.mvLineId});625        if (prop) {626            var amount = data.amount;627            try {628                amount = field_utils.parse.float(data.amount);629            }630            catch (err) {631                amount = NaN;632            }633            // Amount can't be greater than line.amount and can not be negative and must be a number634            // the amount we receive will be a string, so take sign of previous line amount in consideration in order to put635            // the amount in the correct left or right column636            if (amount >= Math.abs(prop.amount) || amount <= 0 || isNaN(amount)) {637                delete prop.partial_amount_str;638                delete prop.partial_amount;639                if (isNaN(amount) || amount < 0) {640                    this.do_warn(_.str.sprintf(_t('The amount %s is not a valid partial amount'), data.amount));641                }642                return this._computeLine(line);643            }644            else {645                var format_options = { currency_id: line.st_line.currency_id };646                prop.partial_amount = (prop.amount > 0 ? 1 : -1)*amount;647                prop.partial_amount_str = field_utils.format.monetary(Math.abs(prop.partial_amount), {}, format_options);648            }649        }650        return this._computeLine(line);651    },652    /**653     * Change the value of the editable proposition line or create a new one.654     *655     * If the editable line comes from a reconcile model with 2 lines656     * and their 'amount_type' is "percent"657     * and their total equals 100% (this doesn't take into account the taxes658     * who can be included or not)659     * Then the total is recomputed to have 100%.660     *661     * @param {string} handle662     * @param {*} values663     * @returns {Promise}664     */665    updateProposition: function (handle, values) {666        var self = this;667        var line = this.getLine(handle);668        var prop = _.last(_.filter(line.reconciliation_proposition, '__focus'));669        if ('to_check' in values && values.to_check === false) {670            // check if we have another line with to_check and if yes don't change value of this proposition671            prop.to_check = line.reconciliation_proposition.some(function(rec_prop, index) {672                return rec_prop.id !== prop.id && rec_prop.to_check;673            });674        }675        if (!prop) {676            prop = this._formatQuickCreate(line);677            line.reconciliation_proposition.push(prop);678        }679        _.each(values, function (value, fieldName) {680            if (fieldName === 'analytic_tag_ids') {681                switch (value.operation) {682                    case "ADD_M2M":683                        // handle analytic_tag selection via drop down (single dict) and684                        // full widget (array of dict)685                        var vids = _.isArray(value.ids) ? value.ids : [value.ids];686                        _.each(vids, function (val) {687                            if (!_.findWhere(prop.analytic_tag_ids, {id: val.id})) {688                                prop.analytic_tag_ids.push(val);689                            }690                        });691                        break;692                    case "FORGET":693                        var id = self.localData[value.ids[0]].ref;694                        prop.analytic_tag_ids = _.filter(prop.analytic_tag_ids, function (val) {695                            return val.id !== id;696                        });697                        break;698                }699            }700            else if (fieldName === 'tax_ids') {701                switch(value.operation) {702                    case "ADD_M2M":703                        prop.__tax_to_recompute = true;704                        if (!_.findWhere(prop.tax_ids, {id: value.ids.id})) {705                            value.ids.price_include = self.taxes[value.ids.id] ? self.taxes[value.ids.id].price_include : false;706                            prop.tax_ids.push(value.ids);707                        }708                        break;709                    case "FORGET":710                        prop.__tax_to_recompute = true;711                        var id = self.localData[value.ids[0]].ref;712                        prop.tax_ids = _.filter(prop.tax_ids, function (val) {713                            return val.id !== id;714                        });715                        break;716                }717            }718            else {719                prop[fieldName] = values[fieldName];720            }721        });722        if ('account_id' in values) {723            prop.account_code = prop.account_id ? this.accounts[prop.account_id.id] : '';724        }725        if ('amount' in values) {726            prop.base_amount = values.amount;727            if (prop.reconcileModelId) {728                this._computeReconcileModels(handle, prop.reconcileModelId);729            }730        }731        if ('force_tax_included' in values || 'amount' in values || 'account_id' in values) {732            prop.__tax_to_recompute = true;733        }734        line.createForm = _.pick(prop, this.quickCreateFields);735        // If you check/uncheck the force_tax_included box, reset the createForm amount.736        if(prop.base_amount)737            line.createForm.amount = prop.base_amount;738        if (prop.tax_ids.length !== 1 ) {739            // When we have 0 or more than 1 taxes, reset the base_amount and force_tax_included, otherwise weird behavior can happen740            prop.amount = prop.base_amount;741            line.createForm.force_tax_included = false;742        }743        return this._computeLine(line);744    },745    /**746     * Format the value and send it to 'account.reconciliation.widget' model747     * Update the number of validated lines748     * overridden in ManualModel749     *750     * @param {(string|string[])} handle751     * @returns {Promise<Object>} resolved with an object who contains752     *   'handles' key753     */754    validate: function (handle) {755        var self = this;756        this.display_context = 'validate';757        var handles = [];758        if (handle) {759            handles = [handle];760        } else {761            _.each(this.lines, function (line, handle) {762                if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) {763                    handles.push(handle);764                }765            });766        }767        var ids = [];768        var values = [];769        var handlesPromises = [];770        _.each(handles, function (handle) {771            var line = self.getLine(handle);772            var props = _.filter(line.reconciliation_proposition, function (prop) {return !prop.invalid;});773            var computeLinePromise;774            if (props.length === 0) {775                // Usability: if user has not chosen any lines and click validate, it has the same behavior776                // as creating a write-off of the same amount.777                props.push(self._formatQuickCreate(line, {778                    account_id: [line.st_line.open_balance_account_id, self.accounts[line.st_line.open_balance_account_id]],779                }));780                // update balance of line otherwise it won't be to zero and another line will be added781                line.reconciliation_proposition.push(props[0]);782                computeLinePromise = self._computeLine(line);783            }784            ids.push(line.id);785            handlesPromises.push(Promise.resolve(computeLinePromise).then(function() {786                var values_dict = {787                    "partner_id": line.st_line.partner_id,788                    "counterpart_aml_dicts": _.map(_.filter(props, function (prop) {789                        return !isNaN(prop.id) && !prop.already_paid;790                    }), self._formatToProcessReconciliation.bind(self, line)),791                    "payment_aml_ids": _.pluck(_.filter(props, function (prop) {792                        return !isNaN(prop.id) && prop.already_paid;793                    }), 'id'),794                    "new_aml_dicts": _.map(_.filter(props, function (prop) {795                        return isNaN(prop.id) && prop.display;796                    }), self._formatToProcessReconciliation.bind(self, line)),797                    "to_check": line.to_check,798                };799                // If the lines are not fully balanced, create an unreconciled amount.800                // line.st_line.currency_id is never false here because its equivalent to801                // statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side).802                // see: get_statement_line_for_reconciliation_widget method in account/models/account_bank_statement.py for more details803                var currency = session.get_currency(line.st_line.currency_id);804                var balance = line.balance.amount;805                if (!utils.float_is_zero(balance, currency.digits[1])) {806                    var unreconciled_amount_dict = {807                        'account_id': line.st_line.open_balance_account_id,808                        'credit': balance > 0 ? balance : 0,809                        'debit': balance < 0 ? -balance : 0,810                        'name': line.st_line.name + ' : ' + _t("Open balance"),811                    };812                    values_dict['new_aml_dicts'].push(unreconciled_amount_dict);813                }814                values.push(values_dict);815                line.reconciled = true;816                self.valuenow++;817            }));818            _.each(self.lines, function(other_line) {819                if (other_line != line) {820                    var filtered_prop = other_line.reconciliation_proposition.filter(p => !line.reconciliation_proposition.map(l => l.id).includes(p.id));821                    if (filtered_prop.length != other_line.reconciliation_proposition.length) {822                        other_line.need_update = true;823                        other_line.reconciliation_proposition = filtered_prop;824                    }825                    self._computeLine(line);826                }827            })828        });829        return Promise.all(handlesPromises).then(function() {830            return self._rpc({831                    model: 'account.reconciliation.widget',832                    method: 'process_bank_statement_line',833                    args: [ids, values],834                    context: self.context,835                })836                .then(self._validatePostProcess.bind(self))837                .then(function () {838                    return {handles: handles};839                });840        });841    },842    //--------------------------------------------------------------------------843    // Private844    //--------------------------------------------------------------------------845    /**846     * add a line proposition after checking receivable and payable accounts constraint847     *848     * @private849     * @param {Object} line850     * @param {Object} prop851     */852    _addProposition: function (line, prop) {853        line.reconciliation_proposition.push(prop);854    },855    /**856     * stop the editable proposition line and remove it if it's invalid then857     * compute the line858     *859     * See :func:`_computeLine`860     *861     * @private862     * @param {string} handle863     * @returns {Promise}864     */865    _blurProposition: function (handle) {866        var line = this.getLine(handle);867        line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (l) {868            l.__focus = false;869            return !l.invalid;870        });871    },872    /**873     * When changing partner, read property_account_receivable and payable874     * of that partner because the counterpart account might cahnge depending875     * on the partner876     *877     * @private878     * @param {string} handle879     * @param {integer} partner_id880     * @returns {Promise}881     */882    _changePartner: function (handle, partner_id) {883        var self = this;884        return this._rpc({885                model: 'res.partner',886                method: 'read',887                args: [partner_id, ["property_account_receivable_id", "property_account_payable_id"]],888            }).then(function (result) {889                if (result.length > 0) {890                    var line = self.getLine(handle);891                    self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0];892                }893            });894    },895    /**896     * Calculates the balance; format each proposition amount_str and mark as897     * invalid the line with empty account_id, amount or label898     * Check the taxes server side for each updated propositions with tax_ids899     * extended by ManualModel900     *901     * @private902     * @param {Object} line903     * @returns {Promise}904     */905    _computeLine: function (line) {906        //balance_type907        var self = this;908        // compute taxes909        var tax_defs = [];910        var reconciliation_proposition = [];911        var formatOptions = {912            currency_id: line.st_line.currency_id,913        };914        line.to_check = false;915        _.each(line.reconciliation_proposition, function (prop) {916            if (prop.to_check) {917                // If one of the proposition is to_check, set the global to_check flag to true918                line.to_check = true;919            }920            if (prop.tax_repartition_line_id) {921                if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) {922                    reconciliation_proposition.push(prop);923                }924                return;925            }926            if (!prop.already_paid && parseInt(prop.id)) {927                prop.is_move_line = true;928            }929            reconciliation_proposition.push(prop);930            if (prop.tax_ids && prop.tax_ids.length && prop.__tax_to_recompute && prop.base_amount) {931                reconciliation_proposition = _.filter(reconciliation_proposition, function (p) {932                    return !p.tax_repartition_line_id || p.link !== prop.id;933                });934                var args = [prop.tax_ids.map(function(el){return el.id;}), prop.base_amount, formatOptions.currency_id];935                var add_context = {'round': true};936                if(prop.tax_ids.length === 1 && line.createForm.force_tax_included)937                    add_context.force_price_include = true;938                tax_defs.push(self._rpc({939                        model: 'account.tax',940                        method: 'json_friendly_compute_all',941                        args: args,942                        context: $.extend({}, self.context || {}, add_context),943                    })944                    .then(function (result) {945                        _.each(result.taxes, function(tax){946                            var tax_prop = self._formatQuickCreate(line, {947                                'link': prop.id,948                                'tax_ids': tax.tax_ids,949                                'tax_repartition_line_id': tax.tax_repartition_line_id,950                                'tag_ids': tax.tag_ids,951                                'amount': tax.amount,952                                'label': prop.label ? prop.label + " " + tax.name : tax.name,953                                'date': prop.date,954                                'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id,955                                'analytic': tax.analytic,956                                '__focus': false957                            });958                            prop.tax_exigible = tax.tax_exigibility === 'on_payment' ? true : undefined;959                            prop.amount = tax.base;960                            prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions);961                            prop.invalid = !self._isValid(prop);962                            tax_prop.amount_str = field_utils.format.monetary(Math.abs(tax_prop.amount), {}, formatOptions);963                            tax_prop.invalid = prop.invalid;964                            reconciliation_proposition.push(tax_prop);965                        });966                        prop.tag_ids = result.base_tags;967                    }));968            } else {969                prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions);970                prop.display = self._isDisplayedProposition(prop);971                prop.invalid = !self._isValid(prop);972            }973        });974        return Promise.all(tax_defs).then(function () {975            _.each(reconciliation_proposition, function (prop) {976                prop.__tax_to_recompute = false;977            });978            line.reconciliation_proposition = reconciliation_proposition;979            var amount_currency = 0;980            var total = line.st_line.amount || 0;981            var isOtherCurrencyId = _.uniq(_.pluck(_.reject(reconciliation_proposition, 'invalid'), 'currency_id'));982            isOtherCurrencyId = isOtherCurrencyId.length === 1 && !total && isOtherCurrencyId[0] !== formatOptions.currency_id ? isOtherCurrencyId[0] : false;983            _.each(reconciliation_proposition, function (prop) {984                if (!prop.invalid) {985                    total -= prop.partial_amount || prop.amount;986                    if (isOtherCurrencyId) {987                        amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency);988                    }989                }990            });991            var company_currency = session.get_currency(line.st_line.currency_id);992            var company_precision = company_currency && company_currency.digits[1] || 2;993            total = utils.round_decimals(total, company_precision) || 0;994            if(isOtherCurrencyId){995                var other_currency = session.get_currency(isOtherCurrencyId);996                var other_precision = other_currency && other_currency.digits[1] || 2;997                amount_currency = utils.round_decimals(amount_currency, other_precision);998            }999            line.balance = {1000                amount: total,1001                amount_str: field_utils.format.monetary(Math.abs(total), {}, formatOptions),1002                currency_id: isOtherCurrencyId,1003                amount_currency: isOtherCurrencyId ? amount_currency : total,1004                amount_currency_str: isOtherCurrencyId ? field_utils.format.monetary(Math.abs(amount_currency), {}, {1005                    currency_id: isOtherCurrencyId1006                }) : false,1007                account_code: self.accounts[line.st_line.open_balance_account_id],1008            };1009            line.balance.show_balance = line.balance.amount_currency != 0;1010            line.balance.type = line.balance.amount_currency ? (line.st_line.partner_id ? 0 : -1) : 1;1011        });1012    },1013    /**1014     *1015     *1016     * @private1017     * @param {string} handle1018     * @param {integer} reconcileModelId1019     */1020    _computeReconcileModels: function (handle, reconcileModelId) {1021        var line = this.getLine(handle);1022        // if quick create with 2 lines who use 100%, change the both values in same time1023        var props = _.filter(line.reconciliation_proposition, {'reconcileModelId': reconcileModelId, '__focus': true});1024        if (props.length === 2 && props[0].percent && props[1].percent) {1025            if (props[0].percent + props[1].percent === 100) {1026                props[0].base_amount = props[0].amount = line.st_line.amount - props[1].base_amount;1027                props[0].__tax_to_recompute = true;1028            }1029        }1030    },1031    /**1032     * format a name_get into an object {id, display_name}, idempotent1033     *1034     * @private1035     * @param {Object|Array} [value] data or name_get1036     */1037    _formatNameGet: function (value) {1038        return value ? (value.id ? value : {'id': value[0], 'display_name': value[1]}) : false;1039    },1040    _formatMany2ManyTags: function (value) {1041        var res = [];1042        for (var i=0, len=value.length; i<len; i++) {1043            res[i] = {'id': value[i][0], 'display_name': value[i][1]};1044        }1045        return res;1046    },1047    _formatMany2ManyTagsTax: function(value) {1048        var res = [];1049        for (var i=0; i<value.length; i++) {1050            res.push({id: value[i], display_name: this.taxes[value[i]] ? this.taxes[value[i]].display_name : ''});1051        }1052        return res;1053    },1054    /**1055     * Format each propositions (amount, label, account_id)1056     * extended in ManualModel1057     *1058     * @private1059     * @param {Object} line1060     * @param {Object[]} props1061     */1062    _formatLineProposition: function (line, props) {1063        var self = this;1064        if (props.length) {1065            _.each(props, function (prop) {1066                prop.amount = prop.debit || -prop.credit;1067                prop.label = prop.name;1068                prop.account_id = self._formatNameGet(prop.account_id || line.account_id);1069                prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str;1070                prop.to_check = !!prop.to_check;1071            });1072        }1073    },1074    /**1075     * Format each server lines and propositions and compute all lines1076     * overridden in ManualModel1077     *1078     * @see '_computeLine'1079     *1080     * @private1081     * @param {Object[]} lines1082     * @returns {Promise}1083     */1084    _formatLine: function (lines) {1085        var self = this;1086        var defs = [];1087        _.each(lines, function (data) {1088            var line = _.find(self.lines, function (l) {1089                return l.id === data.st_line.id;1090            });1091            line.visible = true;1092            line.limitMoveLines = self.limitMoveLines;1093            _.extend(line, data);1094            self._formatLineProposition(line, line.reconciliation_proposition);1095            if (!line.reconciliation_proposition.length) {1096                delete line.reconciliation_proposition;1097            }1098            // No partner set on st_line and all matching amls have the same one: set it on the st_line.1099            defs.push(1100                self._computeLine(line)1101                .then(function(){1102                    if(!line.st_line.partner_id && line.reconciliation_proposition.length > 0){1103                        var hasDifferentPartners = function(prop){1104                            return !prop.partner_id || prop.partner_id != line.reconciliation_proposition[0].partner_id;1105                        };1106                        if(!_.any(line.reconciliation_proposition, hasDifferentPartners)){1107                            return self.changePartner(line.handle, {1108                                'id': line.reconciliation_proposition[0].partner_id,1109                                'display_name': line.reconciliation_proposition[0].partner_name,1110                            }, true);1111                        }1112                    }else if(!line.st_line.partner_id && line.partner_id && line.partner_name){1113                        return self.changePartner(line.handle, {1114                            'id': line.partner_id,1115                            'display_name': line.partner_name,1116                        }, true);1117                    }1118                    return true;1119                })1120                .then(function(){1121                    return data.write_off ? self.quickCreateProposition(line.handle, data.model_id) : true;1122                })1123                .then(function() {1124                    // If still no partner set, take the one from context, if it exists1125                    if (!line.st_line.partner_id && self.context.partner_id && self.context.partner_name) {1126                        return self.changePartner(line.handle, {1127                            'id': self.context.partner_id,1128                            'display_name': self.context.partner_name,1129                        }, true);1130                    }1131                    return true;1132                })1133            );1134        });1135        return Promise.all(defs);1136    },1137    /**1138     * Format the server value then compute the line1139     * overridden in ManualModel1140     *1141     * @see '_computeLine'1142     *1143     * @private1144     * @param {string} handle1145     * @param {Object[]} mv_lines1146     * @returns {Promise}1147     */1148    _formatMoveLine: function (handle, mode, mv_lines) {1149        var self = this;1150        var line = this.getLine(handle);1151        line['mv_lines_'+mode] = _.uniq(line['mv_lines_'+mode].concat(mv_lines), l => l.id);1152        if (mv_lines[0]){1153            line['remaining_'+mode] = mv_lines[0].recs_count - mv_lines.length;1154        } else if (line['mv_lines_'+mode].lenght == 0) {1155            line['remaining_'+mode] = 0;1156        }1157        this._formatLineProposition(line, mv_lines);1158        if ((line.mode == 'match_other' || line.mode == "match_rp") && !line['mv_lines_'+mode].length && !line['filter_'+mode].length) {1159            line.mode = self._getDefaultMode(handle);1160            if (line.mode !== 'match_rp' && line.mode !== 'match_other' && line.mode !== 'inactive') {1161                return this._computeLine(line).then(function () {1162                    return self.createProposition(handle);1163                });1164            }1165        } else {1166            return this._computeLine(line);1167        }1168    },1169    /**1170     * overridden in ManualModel1171     */1172    _getDefaultMode: function(handle) {1173        var line = this.getLine(handle);1174        if (line.balance.amount === 01175            && (!line.st_line.mv_lines_match_rp || line.st_line.mv_lines_match_rp.length === 0)1176            && (!line.st_line.mv_lines_match_other || line.st_line.mv_lines_match_other.length === 0)) {1177            return 'inactive';1178        }1179        if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) {1180            return 'match_rp';1181        }1182        if (line.mv_lines_match_other && line.mv_lines_match_other.length) {1183            return 'match_other';1184        }1185        return 'create';1186    },1187    _getAvailableModes: function(handle) {1188        var line = this.getLine(handle);1189        var modes = []1190        if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) {1191            modes.push('match_rp')1192        }1193        if (line.mv_lines_match_other && line.mv_lines_match_other.length) {1194            modes.push('match_other')1195        }1196        modes.push('create')1197        return modes1198    },1199    /**1200     * Apply default values for the proposition, format datas and format the1201     * base_amount with the decimal number from the currency1202     * extended in ManualModel1203     *1204     * @private1205     * @param {Object} line1206     * @param {Object} values1207     * @returns {Object}1208     */1209    _formatQuickCreate: function (line, values) {1210        values = values || {};1211        var today = new moment().utc().format();1212        var account = this._formatNameGet(values.account_id);1213        var formatOptions = {1214            currency_id: line.st_line.currency_id,1215        };1216        var amount;1217        switch(values.amount_type) {1218            case 'percentage':1219                amount = line.balance.amount * values.amount / 100;1220                break;1221            case 'regex':1222                var matching = line.st_line.name.match(new RegExp(values.amount_from_label_regex))1223                amount = 0;1224                if (matching && matching.length == 2) {1225                    matching = matching[1].replace(new RegExp('\\D' + values.decimal_separator, 'g'), '');1226                    matching = matching.replace(values.decimal_separator, '.');1227                    amount = parseFloat(matching) || 0;1228                    amount = line.balance.amount > 0 ? amount : -amount;1229                }1230                break;1231            case 'fixed':1232                amount = values.amount;1233                break;1234            default:1235                amount = values.amount !== undefined ? values.amount : line.balance.amount;1236        }1237        var prop = {1238            'id': _.uniqueId('createLine'),1239            'label': values.label || line.st_line.name,1240            'account_id': account,1241            'account_code': account ? this.accounts[account.id] : '',1242            'analytic_account_id': this._formatNameGet(values.analytic_account_id),1243            'analytic_tag_ids': this._formatMany2ManyTags(values.analytic_tag_ids || []),1244            'journal_id': this._formatNameGet(values.journal_id),1245            'tax_ids': this._formatMany2ManyTagsTax(values.tax_ids || []),1246            'tag_ids': values.tag_ids,1247            'tax_repartition_line_id': values.tax_repartition_line_id,1248            'debit': 0,1249            'credit': 0,1250            'date': values.date ? values.date : field_utils.parse.date(today, {}, {isUTC: true}),1251            'force_tax_included': values.force_tax_included || false,1252            'base_amount': amount,1253            'percent': values.amount_type === "percentage" ? values.amount : null,1254            'link': values.link,1255            'display': true,1256            'invalid': true,1257            'to_check': !!values.to_check,1258            '__tax_to_recompute': true,1259            '__focus': '__focus' in values ? values.__focus : true,1260        };1261        if (prop.base_amount) {1262            // Call to format and parse needed to round the value to the currency precision1263            var sign = prop.base_amount < 0 ? -1 : 1;1264            var amount = field_utils.format.monetary(Math.abs(prop.base_amount), {}, formatOptions);1265            prop.base_amount = sign * field_utils.parse.monetary(amount, {}, formatOptions);1266        }1267        prop.amount = prop.base_amount;1268        return prop;1269    },1270    /**1271     * Return list of account_move_line that has been selected and needs to be removed1272     * from other calls.1273     *1274     * @private1275     * @returns {Array} list of excluded ids1276     */1277    _getExcludedIds: function () {1278        var excludedIds = [];1279        _.each(this.lines, function(line) {1280            if (line.reconciliation_proposition) {1281                _.each(line.reconciliation_proposition, function(prop) {1282                    if (parseInt(prop['id'])) {1283                        excludedIds.push(prop['id']);1284                    }1285                });1286            }1287        });1288        return excludedIds;1289    },1290    /**1291     * Defined whether the line is to be displayed or not. Here, we only display1292     * the line if it comes from the server or if an account is defined when it1293     * is created1294     * extended in ManualModel1295     *1296     * @private1297     * @param {object} prop1298     * @returns {Boolean}1299     */1300    _isDisplayedProposition: function (prop) {1301        return !isNaN(prop.id) || !!prop.account_id;1302    },1303    /**1304     * extended in ManualModel1305     * @private1306     * @param {object} prop1307     * @returns {Boolean}1308     */1309    _isValid: function (prop) {1310        return !isNaN(prop.id) || prop.account_id && prop.amount && prop.label && !!prop.label.length;1311    },1312    /**1313     * Fetch 'account.reconciliation.widget' propositions.1314     * overridden in ManualModel1315     *1316     * @see '_formatMoveLine'1317     *1318     * @private1319     * @param {string} handle1320     * @returns {Promise}1321     */1322    _performMoveLine: function (handle, mode, limit) {1323        limit = limit || this.limitMoveLines;1324        var line = this.getLine(handle);1325        var excluded_ids = _.map(_.union(line.reconciliation_proposition, line.mv_lines_match_rp, line.mv_lines_match_other), function (prop) {1326            return _.isNumber(prop.id) ? prop.id : null;1327        }).filter(id => id != null);1328        var filter = line['filter_'+mode] || "";1329        return this._rpc({1330                model: 'account.reconciliation.widget',1331                method: 'get_move_lines_for_bank_statement_line',1332                args: [line.id, line.st_line.partner_id, excluded_ids, filter, 0, limit, mode === 'match_rp' ? 'rp' : 'other'],1333                context: this.context,1334            })1335            .then(this._formatMoveLine.bind(this, handle, mode));1336    },1337    /**1338     * format the proposition to send information server side1339     * extended in ManualModel1340     *1341     * @private1342     * @param {object} line1343     * @param {object} prop1344     * @returns {object}1345     */1346    _formatToProcessReconciliation: function (line, prop) {1347        var amount = -prop.amount;1348        if (prop.partial_amount) {1349            amount = -prop.partial_amount;1350        }1351        var result = {1352            name : prop.label,1353            debit : amount > 0 ? amount : 0,1354            credit : amount < 0 ? -amount : 0,1355            tax_exigible: prop.tax_exigible,1356            analytic_tag_ids: [[6, null, _.pluck(prop.analytic_tag_ids, 'id')]]1357        };1358        if (!isNaN(prop.id)) {1359            result.counterpart_aml_id = prop.id;1360        } else {1361            result.account_id = prop.account_id.id;1362            if (prop.journal_id) {1363                result.journal_id = prop.journal_id.id;1364            }1365        }1366        if (!isNaN(prop.id)) result.counterpart_aml_id = prop.id;1367        if (prop.analytic_account_id) result.analytic_account_id = prop.analytic_account_id.id;1368        if (prop.tax_ids && prop.tax_ids.length) result.tax_ids = [[6, null, _.pluck(prop.tax_ids, 'id')]];1369        if (prop.tag_ids && prop.tag_ids.length) result.tag_ids = [[6, null, prop.tag_ids]];1370        if (prop.tax_repartition_line_id) result.tax_repartition_line_id = prop.tax_repartition_line_id;1371        if (prop.reconcileModelId) result.reconcile_model_id = prop.reconcileModelId1372        return result;1373    },1374    /**1375     * Hook to handle return values of the validate's line process.1376     *1377     * @private1378     * @param {Object} data1379     * @param {Object[]} data.moves list of processed account.move1380     * @returns {Deferred}1381     */1382    _validatePostProcess: function (data) {1383        var self = this;1384        return Promise.resolve();1385    },1386});1387/**1388 * Model use to fetch, format and update 'account.move.line' and 'res.partner'1389 * datas allowing manual reconciliation1390 */1391var ManualModel = StatementModel.extend({1392    quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'date', 'to_check'],1393    modes: ['create', 'match'],1394    //--------------------------------------------------------------------------1395    // Public1396    //--------------------------------------------------------------------------1397    /**1398     * Return a boolean telling if load button needs to be displayed or not1399     *1400     * @returns {boolean} true if load more button needs to be displayed1401     */1402    hasMoreLines: function () {1403        if (this.manualLines.length > this.pagerIndex) {1404            return true;1405        }1406        return false;1407    },1408    /**1409     * load data from1410     * - 'account.reconciliation.widget' fetch the lines to reconciliate1411     * - 'account.account' fetch all account code1412     *1413     * @param {Object} context1414     * @param {string} [context.mode] 'customers', 'suppliers' or 'accounts'1415     * @param {integer[]} [context.company_ids]1416     * @param {integer[]} [context.partner_ids] used for 'customers' and1417     *   'suppliers' mode1418     * @returns {Promise}1419     */1420    load: function (context) {1421        var self = this;1422        this.context = context;1423        var domain_account_id = [];1424        if (context && context.company_ids) {1425            domain_account_id.push(['company_id', 'in', context.company_ids]);1426        }1427        var def_account = this._rpc({1428                model: 'account.account',1429                method: 'search_read',1430                domain: domain_account_id,1431                fields: ['code'],1432            })1433            .then(function (accounts) {1434                self.account_ids = _.pluck(accounts, 'id');1435                self.accounts = _.object(self.account_ids, _.pluck(accounts, 'code'));1436            });1437        var domainReconcile = [];1438        var company_ids = context && context.company_ids || [session.company_id];1439        if (company_ids) {1440            domainReconcile.push(['company_id', 'in', company_ids]);1441        }1442        var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile});1443        var def_taxes = this._loadTaxes();1444        return Promise.all([def_reconcileModel, def_account, def_taxes]).then(function () {1445            switch(context.mode) {1446                case 'customers':1447                case 'suppliers':1448                    var mode = context.mode === 'customers' ? 'receivable' : 'payable';1449                    var args = ['partner', context.partner_ids || null, mode];1450                    return self._rpc({1451                            model: 'account.reconciliation.widget',1452                            method: 'get_data_for_manual_reconciliation',1453                            args: args,1454                            context: context,1455                        })1456                        .then(function (result) {1457                            self.manualLines = result;1458                            self.valuenow = 0;1459                            self.valuemax = Object.keys(self.manualLines).length;1460                            var lines = self.manualLines.splice(0, self.defaultDisplayQty);1461                            self.pagerIndex = lines.length;1462                            return self.loadData(lines);1463                        });1464                case 'accounts':1465                    return self._rpc({1466                            model: 'account.reconciliation.widget',1467                            method: 'get_data_for_manual_reconciliation',1468                            args: ['account', context.account_ids || self.account_ids],1469                            context: context,1470                        })1471                        .then(function (result) {1472                            self.manualLines = result;1473                            self.valuenow = 0;1474                            self.valuemax = Object.keys(self.manualLines).length;1475                            var lines = self.manualLines.splice(0, self.defaultDisplayQty);1476                            self.pagerIndex = lines.length;1477                            return self.loadData(lines);1478                        });1479                default:1480                    var partner_ids = context.partner_ids || null;1481                    var account_ids = context.account_ids || self.account_ids || null;1482                    return self._rpc({1483                            model: 'account.reconciliation.widget',1484                            method: 'get_all_data_for_manual_reconciliation',1485                            args: [partner_ids, account_ids],1486                            context: context,1487                        })1488                        .then(function (result) {1489                            // Flatten the result1490                            self.manualLines = [].concat(result.accounts, result.customers, result.suppliers);1491                            self.valuenow = 0;1492                            self.valuemax = Object.keys(self.manualLines).length;1493                            var lines = self.manualLines.splice(0, self.defaultDisplayQty);1494                            self.pagerIndex = lines.length;1495                            return self.loadData(lines);1496                        });1497            }1498        });1499    },1500    /**1501     * Load more partners/accounts1502     * overridden in ManualModel1503     *1504     * @param {integer} qty quantity to load1505     * @returns {Promise}1506     */1507    loadMore: function(qty) {1508        if (qty === undefined) {1509            qty = this.defaultDisplayQty;1510        }1511        var lines = this.manualLines.splice(this.pagerIndex, qty);1512        this.pagerIndex += qty;1513        return this.loadData(lines);1514    },1515    /**1516     * Method to load informations on lines1517     *1518     * @param {Array} lines manualLines to load1519     * @returns {Promise}1520     */1521    loadData: function(lines) {1522        var self = this;1523        var defs = [];1524        _.each(lines, function (l) {1525            defs.push(self._formatLine(l.mode, l));1526        });1527        return Promise.all(defs);1528    },1529    /**1530     * Mark the account or the partner as reconciled1531     *1532     * @param {(string|string[])} handle1533     * @returns {Promise<Array>} resolved with the handle array1534     */1535    validate: function (handle) {1536        var self = this;1537        var handles = [];1538        if (handle) {1539            handles = [handle];1540        } else {1541            _.each(this.lines, function (line, handle) {1542                if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) {1543                    handles.push(handle);1544                }1545            });1546        }1547        var def = Promise.resolve();1548        var process_reconciliations = [];1549        var reconciled = [];1550        _.each(handles, function (handle) {1551            var line = self.getLine(handle);1552            if(line.reconciled) {1553                return;1554            }1555            var props = line.reconciliation_proposition;1556            if (!props.length) {1557                self.valuenow++;1558                reconciled.push(handle);1559                line.reconciled = true;1560                process_reconciliations.push({1561                    id: line.type === 'accounts' ? line.account_id : line.partner_id,1562                    type: line.type,1563                    mv_line_ids: [],1564                    new_mv_line_dicts: [],1565                });1566            } else {1567                var mv_line_ids = _.pluck(_.filter(props, function (prop) {return !isNaN(prop.id);}), 'id');1568                var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id) && prop.display;}), self._formatToProcessReconciliation.bind(self, line));1569                process_reconciliations.push({1570                    id: null,1571                    type: null,1572                    mv_line_ids: mv_line_ids,1573                    new_mv_line_dicts: new_mv_line_dicts1574                });1575            }1576            line.reconciliation_proposition = [];1577        });1578        if (process_reconciliations.length) {1579            def = self._rpc({1580                    model: 'account.reconciliation.widget',1581                    method: 'process_move_lines',1582                    args: [process_reconciliations],1583                });1584        }1585        return def.then(function() {1586            var defs = [];1587            var account_ids = [];1588            var partner_ids = [];1589            _.each(handles, function (handle) {1590                var line = self.getLine(handle);1591                if (line.reconciled) {1592                    return;1593                }1594                line.filter = "";1595                defs.push(self._performMoveLine(handle, 'match').then(function () {1596                    if(!line.mv_lines_match.length) {1597                        self.valuenow++;1598                        reconciled.push(handle);1599                        line.reconciled = true;1600                        if (line.type === 'accounts') {1601                            account_ids.push(line.account_id.id);1602                        } else {1603                            partner_ids.push(line.partner_id.id);1604                        }1605                    }1606                }));1607            });1608            return Promise.all(defs).then(function () {1609                if (partner_ids.length) {1610                    self._rpc({1611                            model: 'res.partner',1612                            method: 'mark_as_reconciled',1613                            args: [partner_ids],1614                        });1615                }1616                return {reconciled: reconciled, updated: _.difference(handles, reconciled)};1617            });1618        });1619    },1620    removeProposition: function (handle, id) {1621        var self = this;1622        var line = this.getLine(handle);1623        var defs = [];1624        var prop = _.find(line.reconciliation_proposition, {'id' : id});1625        if (prop) {1626            line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) {1627                return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link);1628            });1629            line.mv_lines_match.unshift(prop);1630            // No proposition left and then, reset the st_line partner.1631            if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner)1632                defs.push(self.changePartner(line.handle));1633        }1634        line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match';1635        defs.push(this._computeLine(line));1636        return Promise.all(defs).then(function() {1637            return self.changeMode(handle, line.mode, true);1638        })1639    },1640    //--------------------------------------------------------------------------1641    // Private1642    //--------------------------------------------------------------------------1643    /**1644     * override change the balance type to display or not the reconcile button1645     *1646     * @override1647     * @private1648     * @param {Object} line1649     * @returns {Promise}1650     */1651    _computeLine: function (line) {1652        return this._super(line).then(function () {1653            var props = _.reject(line.reconciliation_proposition, 'invalid');1654            _.each(line.reconciliation_proposition, function(p) {1655                delete p.is_move_line;1656            });1657            line.balance.type = -1;1658            if (!line.balance.amount_currency && props.length) {1659                line.balance.type = 1;1660            } else if(_.any(props, function (prop) {return prop.amount > 0;}) &&1661                     _.any(props, function (prop) {return prop.amount < 0;})) {1662                line.balance.type = 0;1663            }1664        });1665    },1666    /**1667     * Format each server lines and propositions and compute all lines1668     *1669     * @see '_computeLine'1670     *1671     * @private1672     * @param {'customers' | 'suppliers' | 'accounts'} type1673     * @param {Object} data1674     * @returns {Promise}1675     */1676    _formatLine: function (type, data) {1677        var line = this.lines[_.uniqueId('rline')] = _.extend(data, {1678            type: type,1679            reconciled: false,1680            mode: 'inactive',1681            limitMoveLines: this.limitMoveLines,1682            filter: "",1683            reconcileModels: this.reconcileModels,1684            account_id: this._formatNameGet([data.account_id, data.account_name]),1685            st_line: data,1686            visible: true1687        });1688        this._formatLineProposition(line, line.reconciliation_proposition);1689        if (!line.reconciliation_proposition.length) {1690            delete line.reconciliation_proposition;1691        }1692        return this._computeLine(line);1693    },1694    /**1695     * override to add journal_id1696     *1697     * @override1698     * @private1699     * @param {Object} line1700     * @param {Object} props1701     */1702    _formatLineProposition: function (line, props) {1703        var self = this;1704        this._super(line, props);1705        if (props.length) {1706            _.each(props, function (prop) {1707                var tmp_value = prop.debit || prop.credit;1708                prop.credit = prop.credit !== 0 ? 0 : tmp_value;1709                prop.debit = prop.debit !== 0 ? 0 : tmp_value;1710                prop.amount = -prop.amount;1711                prop.journal_id = self._formatNameGet(prop.journal_id || line.journal_id);1712                prop.to_check = !!prop.to_check;1713            });1714        }1715    },1716    /**1717     * override to add journal_id on tax_created_line1718     *1719     * @private1720     * @param {Object} line1721     * @param {Object} values1722     * @returns {Object}1723     */1724    _formatQuickCreate: function (line, values) {1725        // Add journal to created line1726        if (values && values.journal_id === undefined && line && line.createForm && line.createForm.journal_id) {1727            values.journal_id = line.createForm.journal_id;1728        }1729        return this._super(line, values);1730    },1731    /**1732     * @override1733     * @param {object} prop1734     * @returns {Boolean}1735     */1736    _isDisplayedProposition: function (prop) {1737        return !!prop.journal_id && this._super(prop);1738    },1739    /**1740     * @override1741     * @param {object} prop1742     * @returns {Boolean}1743     */1744    _isValid: function (prop) {1745        return prop.journal_id && this._super(prop);1746    },1747    /**1748     * Fetch 'account.move.line' propositions.1749     *1750     * @see '_formatMoveLine'1751     *1752     * @override1753     * @private1754     * @param {string} handle1755     * @returns {Promise}1756     */1757    _performMoveLine: function (handle, mode, limit) {1758        limit = limit || this.limitMoveLines;1759        var line = this.getLine(handle);1760        var excluded_ids = _.map(_.union(line.reconciliation_proposition, line.mv_lines_match), function (prop) {1761            return _.isNumber(prop.id) ? prop.id : null;1762        }).filter(id => id != null);1763        var filter = line.filter || "";1764        var args = [line.account_id.id, line.partner_id, excluded_ids, filter, 0, limit];1765        return this._rpc({1766                model: 'account.reconciliation.widget',1767                method: 'get_move_lines_for_manual_reconciliation',1768                args: args,1769                context: this.context,1770            })1771            .then(this._formatMoveLine.bind(this, handle, ''));1772    },1773    _formatToProcessReconciliation: function (line, prop) {1774        var result = this._super(line, prop);1775        result['date'] = prop.date;1776        return result;1777    },1778    _getDefaultMode: function(handle) {1779        var line = this.getLine(handle);1780        if (line.balance.amount === 0 && (!line.st_line.mv_lines_match || line.st_line.mv_lines_match.length === 0)) {1781            return 'inactive';1782        }1783        return line.mv_lines_match.length > 0 ? 'match' : 'create';1784    },1785    _formatMoveLine: function (handle, mode, mv_lines) {1786        var self = this;1787        var line = this.getLine(handle);1788        line.mv_lines_match = _.uniq((line.mv_lines_match || []).concat(mv_lines), l => l.id);1789        this._formatLineProposition(line, mv_lines);1790        if (line.mode !== 'create' && !line.mv_lines_match.length && !line.filter.length) {1791            line.mode = this.avoidCreate || !line.balance.amount ? 'inactive' : 'create';1792            if (line.mode === 'create') {1793                return this._computeLine(line).then(function () {1794                    return self.createProposition(handle);1795                });1796            }1797        } else {1798            return this._computeLine(line);1799        }1800    },1801});1802return {1803    StatementModel: StatementModel,1804    ManualModel: ManualModel,1805};...

Full Screen

Full Screen

estiModel.js

Source:estiModel.js Github

copy

Full Screen

1/**2 * Created by kingw on 2015-10-31.3 */4var mysql = require('mysql');5var db_config = require('./db_config');6var pool = mysql.createPool(db_config);7var logger = require('../logger');8var async = require('async');9/*******************10 *  Estimate Song List11 ********************/12exports.estiSongList = function(done){13    var sql =14        "SELECT DISTINCT(song_idx) song_idx, s.song_song, s.song_video, s.song_comment, u.user_freq, u.user_nickname " +15        "FROM atn_song s, atn_user u " +16        "WHERE s.song_user = u.user_idx " +17        "ORDER BY RAND() LIMIT 5";  // 5곡18    pool.query(sql, function(err, rows){19        if(err){20            logger.error("Estimate Song Send Error: ", err);21            done(false, "Estimate Song Send Error");22        }else{23            if(rows.length == 0){24                logger.error("Estimate Song Send no data");25                done(false, "Estimate Song Send Error");  // done callback26            }else{27                done(true, "success", rows);28            }29        }30    });31};32/*******************33 *  Estimate Song Result34 ********************/35exports.estiResultSongFirst = function(data, done){36    pool.getConnection(function(err, conn){37        if(err){38            logger.error("Estimate Result First getConnection error:", err);39            done(false, "Estimate Result First getConnection error");40        }else{41            conn.beginTransaction(function(err){42                if(err){43                    logger.error("Estimate Result First beginTransaction error:", err);44                    done(false, "Estimate Result First beginTransaction error");45                    conn.release();46                }else{47                    async.waterfall([48                            function(callback){49                                var sql = "INSERT INTO atn_user() VALUE ()";50                                conn.query(sql, function(err, rows){51                                    if(err){52                                        logger.error("Estimate Result First waterfall_1:", err);53                                        callback(err);54                                    }else{55                                        if(rows.length == 0){56                                            logger.error("Estimate Result First waterfall_2: no data");57                                            conn.rollback(function(){58                                                done(false, "Estimate Result First DB Error");  // error done callback59                                                conn.release();60                                            });61                                        }else{62                                            logger.info("rows.insertId:", rows.insertId);63                                            callback(null, rows.insertId);64                                        }65                                    }66                                });67                            },68                            function(user_idx, callback){69                                data.esti_user = user_idx;  // 객체에 esti_user 추가70                                var sql = "INSERT INTO atn_esti SET ?";71                                conn.query(sql, data, function(err, rows){72                                    if(err){73                                        logger.error("Estimate Result First waterfall_3:", err);74                                        callback(err);75                                    }else{76                                        if(rows.affectedRows == 1){77                                            callback(null, user_idx);78                                        }else{79                                            conn.rollback(function(){80                                                logger.error("Estimate Result First waterfall_4");81                                                done(false, "Estimate Result First DB Error");  // error done callback82                                                conn.release();83                                            });84                                        }85                                    }86                                });87                            }88                        ],89                        function(err, user_idx){90                            if(err){91                                conn.rollback(function(){92                                    done(false, "Estimate Result First DB Error");  // error93                                    conn.release();94                                });95                            }else{96                                conn.commit(function(err){97                                    if(err){98                                        logger.error("Estimate Result First Commit Error:", err);99                                        done(false, "Estimate Result First Commit Error");  // error100                                        conn.release();101                                    }else{102                                        done(true, "success", user_idx);  // success103                                        conn.release();104                                    }105                                });106                            }107                        }108                    );  // waterfall109                }110            });  // beginTransaction111        }112    });113};114exports.estiResultSong = function(data, done){115    async.waterfall([116            function(callback){117                var sql = "SELECT * FROM atn_esti WHERE esti_user=? AND esti_song=?";118                pool.query(sql, [data.esti_user, data.esti_song], function(err, rows){119                    if(err){120                        logger.error("Estimate Result Waterfall Error_1:", err);121                        callback(err);122                    }else{123                        callback(null, rows);124                    }125                });126            },127            function(flag, callback){128                logger.info("flag:",flag);129                if(flag.length == 0){  // 평가하지 않은 곡130                    var insert_sql = "INSERT INTO atn_esti SET ?";131                    pool.query(insert_sql, data, function(err, rows){132                        if(err){133                            logger.error("Estimate Result Waterfall Error_2:", err);134                            callback(err);135                        }else{136                            if(rows.affectedRows != 1){137                                logger.error("Estimate Result Waterfall Error_3");138                                done(false, "Estimate Result DB Error");  // callback 없이 done139                            }else{140                                callback(null);141                            }142                        }143                    });144                }else{  // 이미 평가한곡145                    var update_sql = "UPDATE atn_esti SET esti_esti=? WHERE esti_user=?  AND esti_song=?";146                    pool.query(update_sql, [data.esti_esti, data.esti_user, data.esti_song], function(err, rows){147                        if(err){148                            logger.error("Estimate Result Waterfall Error_4:", err);149                            callback(err);150                        }else{151                            if(rows.affectedRows != 1){152                                logger.error("Estimate Result Waterfall Error_5");153                                done(false, "Estimate Result DB Error");  // callback 없이 done154                            }else{155                                callback(null);156                            }157                        }158                    });159                }160            }161        ],162        function(err){163            if(err){164                done(false, "Estimate Result Error");165            }else{166                done(true, "success");167            }168        }169    );170};171/*******************172 *  Estimate Match173 ********************/174exports.estimate = function(uid, done){175    var sql = "SELECT esti_song, esti_esti FROM atn_esti WHERE esti_user = ?";176    pool.query(sql, uid, function(err, rows){177        if(err){178            logger.error("Estimate error:", err);179            done(err);180        }else{181            if(rows.length == 0){182                logger.error("No Estimate Data");183                done("No Estimate Data");  // my error code184            }else{185                done(null, rows);186            }187        }188    });189};190exports.otherList = function(uid, done){191    var sql =192        "SELECT user_idx, user_freq " +193        "FROM atn_user " +194        "WHERE user_freq IS NOT NULL AND user_idx != 1 AND user_idx != ? AND " +195        "user_idx NOT IN (SELECT bookmark_friend FROM atn_bookmark WHERE bookmark_my = ?)";  // 1은 운영자196    pool.query(sql, [uid, uid], function(err, rows){197        if(err){198            logger.error("Other List error:", err);199            done(err);200        }else{201            if(rows.length == 0){202                logger.error("Other List No user");203                done("Other List No user");  // my error code204            }else{205                done(null, rows);206            }207        }208    });209};210exports.matchInsert = function(uid, other_idx, data, done){211    pool.getConnection(function(err, conn){212        if(err){213            logger.error("Match Insert getConnection error:", err);214            done(false, "Match Insert getConnection error");215        }else{216            conn.beginTransaction(function(err){217                if(err){218                    logger.error("Match Insert beginTransaction error:", err);219                    done(false, "Match Insert beginTransaction error");220                    conn.release();221                }else{222                    async.waterfall([223                            function(callback){224                                var sql = "Update atn_user SET user_partner=? WHERE user_idx=?";225                                conn.query(sql, [other_idx, uid], function(err, rows){226                                    if(err){227                                        logger.error("Match Insert waterfall_1:", err);228                                        callback(err);229                                    }else{230                                        if(rows.affectedRows != 1){231                                            logger.error("Match Insert waterfall_2: no data");232                                            conn.rollback(function(){233                                                done(false, "Match Insert DB Error");  // error done callback234                                                conn.release();235                                            });236                                        }else{237                                            callback(null);238                                        }239                                    }240                                });241                            },242                            function(callback){  // 기존 매칭 결과 삭제243                                var sql = "SELECT COUNT(*) cnt FROM atn_match WHERE match_other=? AND match_my=?";244                                conn.query(sql, [other_idx, uid], function(err, rows){245                                    if(err){246                                        logger.error("Match Insert waterfall_3:", err);247                                        callback(err);248                                    }else{249                                        if(rows[0].cnt){250                                            var delete_sql = "DELETE FROM atn_match WHERE match_other=? AND match_my=?";251                                            conn.query(delete_sql, [other_idx, uid], function(err, rows){252                                                if(err){253                                                    logger.error("Match Insert waterfall_4:", err);254                                                    callback(err);255                                                }else{256                                                    callback(null);257                                                }258                                            });259                                        }else{260                                            callback(null);261                                        }262                                    }263                                });264                            },265                            function(callback){266                                if(data.length == 0){  // 매칭 곡이 아예 없을때267                                    callback(null);268                                }else{269                                    var sql = "INSERT INTO atn_match(match_my, match_other, match_song) VALUES ?";270                                    logger.info("data:", [data]);271                                    conn.query(sql, [data], function(err, rows){272                                        if(err){273                                            logger.error("Match Insert waterfall_5:", err);274                                            callback(err);275                                        }else{276                                            if(rows.affectedRows == 0){277                                                conn.rollback(function(){278                                                    logger.error("Match Insert waterfall_6");279                                                    done(false, "Match Insert DB Error");  // error done callback280                                                    conn.release();281                                                });282                                            }else{283                                                callback(null);284                                            }285                                        }286                                    });287                                }288                            }289                        ],290                        function(err){291                            if(err){292                                conn.rollback(function(){293                                    done(false, "Match Insert DB Error");  // error294                                    conn.release();295                                });296                            }else{297                                conn.commit(function(err){298                                    if(err){299                                        logger.error("Match Insert Commit Error:", err);300                                        done(false, "Match Insert Commit Error");  // error301                                        conn.release();302                                    }else{303                                        done(true, "success");  // success304                                        conn.release();305                                    }306                                });307                            }308                        }309                    );  // waterfall310                }311            });  // beginTransaction312        }313    });314};315/*******************316 *  Estimate Detail317 ********************/318exports.estiDetail = function(data, done){319    async.waterfall([320            function(callback){321                var sql = "SELECT user_partner FROM atn_user WHERE user_idx = ?";322                pool.query(sql, data, function(err, rows){323                    if(err){324                        logger.error("Estimate Detail waterfall_1: ", err);325                        callback(err);326                    }else{327                        if(rows.length == 0){328                            logger.error("No Estimate Detail Data_1");329                            done(false, "No Estimate Detail Data_1");  // my error code330                        }else{331                            callback(null, rows[0].user_partner);332                        }333                    }334                });335            },336            function(partner_idx, callback){337                logger.info("partner_idx:", partner_idx);338                var sql =339                    "SELECT user_freq, user_song, user_video, user_nickname, user_comment " +340                    "FROM atn_user " +341                    "WHERE user_idx = ?";342                pool.query(sql, partner_idx, function(err, rows){343                    if(err){344                        logger.error("Estimate Detail waterfall_2: ", err);345                        callback(err);346                    }else{347                        if(rows.length == 0){348                            logger.error("No Estimate Detail Data_2");349                            done(false, "No Estimate Detail Data_2");  // my error code350                        }else{351                            callback(null, rows[0], partner_idx);352                        }353                    }354                });355            },356            function(partner, partner_idx, callback){357                logger.info("partner:", partner);358                var sql = "SELECT match_song FROM atn_match WHERE match_my = ? AND match_other = ?";359                pool.query(sql, [data, partner_idx], function(err, rows){360                    if(err){361                        logger.error("Estimate Detail waterfall_3: ", err);362                        callback(err);363                    }else{364                        if(rows.length == 0){365                            logger.error("No Estimate Detail Data_3");366                            done(false, "No Estimate Detail Data_3");  // my error code367                        }else{368                            var song = [];369                            var song_sql =370                                "SELECT song_song, song_video, song_comment " +371                                "FROM atn_song " +372                                "WHERE song_idx = ?";373                            async.each(rows, function(song_check, each_cb){374                                    logger.info("song_check:", song_check);375                                    var like_check = 1;376                                    var song_idx = song_check.match_song;377                                    if(song_check.match_song < 0){378                                        song_idx = song_idx * -1;379                                        like_check = -1;380                                    }381                                    pool.query(song_sql, song_idx, function(err, rows){382                                        if(err){383                                            logger.error("Estimate Detail Each Error:", err);384                                            each_cb(err);385                                        }else{386                                            logger.info("songs:", rows[0]);387                                            song.push({388                                                "song": rows[0].song_song,389                                                "video": rows[0].song_video,390                                                "comment": rows[0].song_comment,391                                                "like": like_check392                                            });393                                            each_cb();394                                        }395                                    });396                                },397                                function(err){398                                    if(err){399                                        callback(err);400                                    }else{401                                        callback(null, song, partner);402                                    }403                                }404                            );  // each405                        }406                    }407                });408            }409        ],410        function(err, song, partner){411            if(err){412                done(false, "Estimate Detail Error");413            }else{414                done(true, "success", song, partner);415            }416        }417    );  // waterfall418};419/*******************420 *  Estimate Random421 ********************/422exports.estiRandom = function(uid, done){423    var sql =424        "SELECT user_idx, user_freq " +425        "FROM atn_user " +426        "WHERE user_freq IS NOT NULL AND user_idx != 1 AND user_idx != ? "+427        "ORDER BY RAND() LIMIT 1";  // 10곡// 21은 운영자...

Full Screen

Full Screen

Snakefile

Source:Snakefile Github

copy

Full Screen

1#BATCHES = [x for x in range(1,8)]2BATCHES = ["1"]3#WHITELIST_USE=TRUE4WHITELIST_EXPECTCELLS=965WHITELIST_CORRECT_THRESH=26GENOMEDIR="/storage/static_data/mm10/"7MAX_THREAD=48REF_GTF="/storage/static_data/mm10/mm10.gtf"9#BARCODE_FORMAT="CCCCCCNNNNNN"10BARCODE_FORMAT="NNNNNNCCCCCC"11BARCODE_EXTRACT="string"12BARCODES_EVEN="input_dir/barcodes001-096.txt"13BARCODES_ODD ="input_dir/barcodes097-192.txt"14results_dir="results_"+BARCODE_FORMAT15results_sample=results_dir+"/{sample}"16rule all:17   input:18       results_dir + "/complete_count_matrix.tsv"19rule mergeCounts:20    input:21        expand(results_sample + "/6_renamedmatrix/relabelled.matrix", sample = BATCHES)22    output:23        results_dir + "/complete_count_matrix.tsv"24    shell:25        "join -j 1 {input} > {output}"26rule renameCountHeaders:27    input:28        results_sample + "/5_countmatrix/counts.matrix"29    output:30        results_sample + "/6_renamedmatrix/relabelled.matrix"31    run:32        shell('''33        cat {input} | sed 1 s|\b[0-9]+_([ACTG]+)[_0-9.tx]+\b|F{sample}_\1|g > {output}34        ''')35rule countGenesPerCell:36    input:37        results_sample + "/4_sortedbam/sorted.bam"38    output:39        results_sample + "/5_countmatrix/counts.matrix"40    shell:41        "umi_tools counts --per-gene --gene-tag=XT --per-cell -I {input} -S {output}"42rule reindexBam:43    input:44        results_sample + "/3_featurecounts/Aligned.sortedByCoord.out.bam.featureCounts.bam"45    output:46        bam=results_sample + "/4_sortedbam/sorted.bam",47        bai=results_sample + "/4_sortedbam/sorted.bam.bai"48    run:49        shell("samtools sort {input} -o {output.bam}"),50        shell("samtools index {output.bam}")51rule reads2Genes:52    input:53        results_sample + "/2_starmap/Aligned.sortedByCoord.out.bam"54    output:55        bam=results_sample + "/3_featurecounts/Aligned.sortedByCoord.out.bam.featureCounts.bam",56        cm=results_sample + "/3_featurecounts/counts.tsv"57    params:58        threads=MAX_THREAD,59        gtf=REF_GTF,60    run:61        shell("featureCounts -a {params.gtf} -o {output.cm}  -R BAM {input} -T {params.threads}"),62        shell("mv Aligned.sortedByCoord.out.bam.featureCounts.bam {output}")63rule mapReads:64    input:65        results_sample + "/1_umi_extract/R2_extracted.fastq"66    output:67        results_sample + "/2_starmap/Aligned.sortedByCoord.out.bam"68    params:69        gendir=GENOMEDIR,70        threads=MAX_THREAD,71        outfilterMMMax=1,72        readcommand="zcat",73        outSAMtype="BAM"74    run:75        shell('''76        STAR77        --readFilesIn {input}78        --runThreadN {params.threads}79        --genomeDir {params.gendir}80        --readFilesCommand {params.readcommand}81        --outFilterMultimapNmax {params.outfilterMMMax}82        --outSAMtype {params.outSAMtype} SortedByCoordinate83        '''),84        shell("mv Aligned.sortedByCoord.out.bam {output}")85rule whitelistBCsAndUmis:86    input:87        read1="input_dir/FACS{sample}_R1.fastq",88        read2="input_dir/FACS{sample}_R2.fastq",89        bar_even=results_sample + "/barcode_even.extracted",90        bar_odd =results_sample + "/barcode_odd.extracted"91    output:92        whitelist=results_sample + "/0_umi_whitelist/guessed_barcodes",93        log=      results_sample + "/0_umi_whitelist/whitelist.log"94    params:95        plotprefix=results_sample + "/plots",96        expect_cells=WHITELIST_EXPECTCELLS,97        correct_thresh=WHITELIST_CORRECT_THRESH,98        bc_pattern=BARCODE_FORMAT,99        extract_method=BARCODE_EXTRACT100    shell:101        '''102        umi_tools whitelist\103            --extract-method='{params.extract_method}'\104            --bc-pattern='{params.bc_pattern}'\105            --set-cell-number={params.expect_cells}\106            --error-correct-threshold={params.correct_thresh}\107            --plot-prefix='{params.plotprefix}'\108            --method=umis\109            --stdin='{input.read1}'\110            --log='{output.log}'\111           --stdout {output.whitelist}112        '''113rule measureBarcodeOverlapWithWhitelist:114    input:115        guessed=results_sample + "/0_umi_whitelist/guessed_barcodes",116        bar_even=results_sample + "/__preproc/barcode_even.extracted",117        bar_odd =results_sample + "/__preproc/barcode_odd.extracted"118    output:119        results_sample + "/0_umi_whitelist/stats.txt"120    run:121        bar_used=input.bar_even if int(wildcards.sample)%2==0 else input.bar_odd122        designed_barcodes = {}123        MATCH_MAIN='matches_ḿain'124        MATCH_OTHER='matches_other'125        126        with open(bar_used, 'r') as f:127            for line in f:128                #num, barcode = line.split()129                barcode = line.strip()130                if barcode not in designed_barcodes:131                    designed_barcodes[barcode] = {132                        MATCH_MAIN : [],133                        MATCH_OTHER: []134                    }135                else:136                    print("Duplicate:", barcode, file=sys.stderr)137        with open(input.guessed, 'r') as f:138            for line in f:139                tokens = line.split()140                barcode_main = tokens[0].strip()141                barcode_others = map(lambda x: x.strip(), tokens[1].split(','))142                # Direct match143                if barcode_main in designed_barcodes:144                    designed_barcodes[barcode_main][MATCH_MAIN].append(barcode_main)145                # Matches with others146                for bar_other in barcode_others:147                    if bar_other in designed_barcodes:148                        # This other barcode exists in the designed barcodes, heres the149                        # main barcode this other barcode it belongs to150                        designed_barcodes[bar_other][MATCH_OTHER].append(barcode_main)151        with open(output[0], 'w') as fout:152            # Process map153            print("Wanted\tDirectMatch\tMergedWithOther", file=fout)154            155            for wanted_barcode in designed_barcodes:156                matches_main  = designed_barcodes[wanted_barcode][MATCH_MAIN]157                match_w_other = designed_barcodes[wanted_barcode][MATCH_OTHER]158                print("%s\t%s\t%s" % (159                    wanted_barcode, ','.join(matches_main), ','.join(match_w_other)160                ), file=fout)161        fout.close()162rule extractBCsAndUmis:163    input:164        stats=results_sample + "/0_umi_whitelist/stats.txt",165        # stats are not a required input, but good to be generated here166        read1="input_dir/FACS{sample}_R1.fastq",167        read2="input_dir/FACS{sample}_R2.fastq",168        whitelist=results_sample + "/0_umi_whitelist/guessed_barcodes"169    output:170        read1=results_sample + "/1_umi_extract/R1_extracted.fastq",171        read2=results_sample + "/1_umi_extract/R2_extracted.fastq",172        log=results_sample + "/1_umi_extract/log.txt"173    params:174        bc_pattern=BARCODE_FORMAT,175        extract_method=BARCODE_EXTRACT176    shell:177        '''178        umi_tools extract179        --error-correct-cell180        --filter-cell-barcode181        --whitelist='{input.whitelist}'182        --extract-method='{params.extract_method}'183        --bc-pattern='{params.bc_pattern}'184        --stdin='{input.read1}'185        --read2-in='{input.read2}'186        --stdout='{output.read1}'187        --read2-out='{output.read2}'188        --log='{output.log}'189        '''190rule extractBarcodes:191    input:192        odd=BARCODES_ODD,193        even=BARCODES_EVEN194    output:195        odd= results_sample + "/__preproc/barcode_odd.extracted",196        even=results_sample + "/__preproc/barcode_even.extracted"197    run:198        shell("cut -f 2 {input.odd} > {output.odd}"  ),...

Full Screen

Full Screen

flashcards.py

Source:flashcards.py Github

copy

Full Screen

1import os2import io3from random import choice4import argparse5ADD = "add"6REMOVE = "remove"7IMPORT = "import"8EXPORT = "export"9ASK = "ask"10EXIT = "exit"11LOG = "log"12HARDEST_CARD = "hardest card"13RESET = "reset stats"14# set up logger15output = io.StringIO()16cards = {}17def add():18    front = input("The card:\n")19    print(f'The card:\n{front}', file=output)20    while front in cards:21        front = input(f'The term "{front}" already exists. Try again:\n')22        print(f'The term "{front}" already exists. Try again:\n{front}', file=output)23    back = input("The definition of the card:\n")24    print(f'The definition of the card:\n{back}', file=output)25    while back in [value["definition"] for key, value in cards.items()]:26        back = input(f'The definition "{back}" already exists. Try again:\n')27        print(f'The definition "{back}" already exists. Try again:\n{back}', file=output)28    cards[front] = {29        "definition": back,30        "mistake": 031    }32    print(f'The pair ("{front}":"{back}") has been added.')33    print(f'The pair ("{front}":"{back}") has been added.', file=output)34def remove():35    front = input("Which card?\n")36    print(f'Which card?\n{front}', file=output)37    if front in cards:38        cards.pop(front)39        print("The card has been removed.")40        print("The card has been removed.", file=output)41    else:42        print(f'Can\'t remove "{front}": there is no such card.')43        print(f'Can\'t remove "{front}": there is no such card.', file=output)44def _import(file_path):45    if os.access(file_path, os.F_OK):46        new_dict = {}47        with open(file_path) as file:48            data = file.read().splitlines()49            for card in data:50                front, back, count = card.split(", ")51                new_dict[front] = {52                    "definition": back,53                    "mistake": int(count)54                }55        print(f"{len(new_dict)} cards have been loaded.")56        print(f"{len(new_dict)} cards have been loaded.", file=output)57        cards.update(new_dict)58    else:59        print("File not found.")60        print("File not found.", file=output)61def export(file_path):62    with open(file_path, "w") as file:63        for front, value in cards.items():64            print(f"{front}, {value['definition']}, {value['mistake']}", file=file)65    print(f"{len(cards)} cards have been saved.")66    print(f"{len(cards)} cards have been saved.", file=output)67def ask():68    n_ask = int(input("How many times to ask?\n").strip())69    print(f'How many times to ask?\n{n_ask}', file=output)70    card_list = list(cards.items())71    while n_ask > 0:72        random_card = choice(card_list)73        front, value = random_card74        definition = value["definition"]75        answer = input(f'Print the definition of "{front}":\n')76        print(f'Print the definition of "{front}":\n{answer}', file=output)77        if answer == definition:78            print("Correct!")79            print("Correct!", file=output)80        else:81            match_other = ""82            for other_term, other_value in cards.items():83                if answer == other_value["definition"]:84                    match_other = other_value85                    break86            if match_other:87                print(f'Wrong. The right answer is "{definition}", '88                      f'but your definition is correct for "{match_other}"')89                print(f'Wrong. The right answer is "{definition}", '90                      f'but your definition is correct for "{match_other}"', file=output)91            else:92                print(f'Wrong. The right answer is "{definition}".')93                print(f'Wrong. The right answer is "{definition}".', file=output)94            cards[front]["mistake"] += 195        n_ask -= 196def _exit(file_path):97    print("Bye bye!")98    print("Bye bye!", file=output)99    if file_path:100        export(file_path)101    quit()102def log():103    file_name = input("File name:\n").strip()104    print(f'File name:\n{file_name}', file=output)105    with open(file_name, "w") as opened_file:106        contents = output.getvalue()107        print(contents, file=opened_file)108    print("The log has been saved.")109    print("The log has been saved.", file=output)110def hardest_card():111    # find the highest mistake count and card lists112    highest = 0113    hard_card_list = []114    for term, value in cards.items():115        mistake = value["mistake"]116        if mistake == 0:117            continue118        elif mistake > highest:119            hard_card_list = [term]120            highest = mistake121        elif mistake == highest:122            hard_card_list.append(term)123    # give the results124    if not hard_card_list:125        print("There are no cards with errors.")126        print("There are no cards with errors.", file=output)127    elif len(hard_card_list) == 1:128        print(f'The hardest card is "{hard_card_list[0]}". You have {highest} errors answering it.')129        print(f'The hardest card is "{hard_card_list[0]}". You have {highest} errors answering it.', file=output)130    else:131        card_string = '", "'.join(hard_card_list)132        print(f'The hardest cards are "{card_string}". You have {highest} errors answering them.')133        print(f'The hardest cards are "{card_string}". You have {highest} errors answering them.', file=output)134def reset():135    for card, value in cards.items():136        value["mistake"] = 0137    print("Card statistics have been reset.")138    print("Card statistics have been reset.", file=output)139def main():140    parser = argparse.ArgumentParser(description="This program show flashcards and save your progress.")141    parser.add_argument("--import_from", help="Provide the file path of your progress.")142    parser.add_argument("--export_to", help="Prove the file path that you would like to save your progress.")143    args = parser.parse_args()144    if args.import_from:145        _import(args.import_from)146    while True:147        func = input("\nInput the action "148                     "(add, remove, import, export, ask, exit, log, hardest card, reset stats)\n").strip().lower()149        print(f'\nInput the action (add, remove, import, export, ask, exit, log, hardest card, reset stats)'150              f'\nFile name:\n{func}', file=output)151        if func == ADD:152            add()153        elif func == REMOVE:154            remove()155        elif func == IMPORT:156            file_name = input("File name:\n").strip()157            print(f'File name:\n{file_name}', file=output)158            _import(file_name)159        elif func == EXPORT:160            file_name = input("File name:\n").strip()161            print(f'File name:\n{file_name}', file=output)162            export(file_name)163        elif func == ASK:164            ask()165        elif func == EXIT:166            _exit(args.export_to)167        elif func == LOG:168            log()169        elif func == HARDEST_CARD:170            hardest_card()171        elif func == RESET:172            reset()173if __name__ == "__main__":...

Full Screen

Full Screen

test_script.py

Source:test_script.py Github

copy

Full Screen

1# -*- coding: utf-8 -*-2"""3Created on Tue Feb 10 13:36:27 20154@author: dehua5"""6import subprocess7import os8import json9#config_xml = 'parameter-config.xml'10face_recog_file = '/Release/FaceRecog_refactor'11decrypt_jar = 'DecryptFiles.jar'12template_root_path = '/home/aicu01admin/apps/aiview_dashboard_ror_nida/current/public/system/template/'13video_root_path = '/home/aicu01admin/apps/aiview_dashboard_ror_nida/current/public/system/uploads/video/'14#template_root_path = '/home/lei/test_data/system/template/'15#video_root_path = '/home/lei/test_data/system/uploads/video/'16out_file_name = 'outputs.txt'17patient_list = ['patient6',18                'patient8'19		""",20                'patient10',21                'patient12',22                'patient14',23                'patient16',24                'patient18',25                'patient20',26                'patient22',27                'patient23',28                'patient25',29                'patient27',30                'patient28',31                'patient30'32		"""33		]34                35def generate_param_xml():36    param_folder = "./param/"37    xml_template = open(param_folder+"parameter-config.template", "r")38    data = xml_template.read()39    data_str = str(data)40    41    confident = 042    qulity = 043    max_roll = 044    max_yaw = 045    46    xmls = []47    while confident < 50:48        confident += 1049        qulity = 050        while qulity < 128:51            qulity += 6452            max_roll = 053            while max_roll < 40:54                max_roll += 1055                max_yaw = 056                while max_yaw < 30:57                    max_yaw += 1058                    s = data_str.replace('VALUE_FACE_CONFI_THRESH', str(confident))59                    s = s.replace('VALUE_FACE_QUALI_THRESH', str(qulity))60                    s = s.replace('VALUE_MAX_ROLL', str(max_roll))61                    s = s.replace('VALUE_MAX_YAW', str(max_yaw))62                    filename = param_folder + "parameter-config_"+str(confident)+"_"+str(qulity)+"_"+str(max_roll)+"_"+str(max_yaw)+".xml"63                    print filename64                    xmls.append(filename)65                    temp = open(filename, "w")66                    temp.write(s)67                    temp.close();68    xml_template.close()69    return xmls70    71    72def walk(path):73    fileSet = []74    total = 0 75    for root, dirs, files in os.walk(path):76        for file in files:77            fileName = os.path.join(root, file)78            if (fileName.find(".flv") != -1 and fileName.find("blur") == -1 and fileName.find("mux")==-1 ):79                if (fileName.find("dev") == -1):80                    decryptFile = fileName+'dev.flv'81                    if os.path.exists(decryptFile) == False:82                        arg = 'java -jar '+decrypt_jar + ' ' + fileName + ' ' + decryptFile83                        p = subprocess.Popen(arg, stdout=subprocess.PIPE,shell=True)84                        out, err = p.communicate()85                    fileSet.append(decryptFile)86                    total += 187    return fileSet88    89def clean(path):90    for root, dirs, files in os.walk(path):91        for file in files:92            fileName = os.path.join(root, file)93            if (fileName.find("dev.flv") != -1 ):94                print 'rm ' + fileName95                p = subprocess.Popen('rm ' + fileName, stdout=subprocess.PIPE,shell=True)96                out, err = p.communicate()97    98def prepareJson(input_video, self_template_path, all_template, config_xml):99    input_json = '"{\\"parameter_config_xml_path\\":\\"'+config_xml100    input_json +='\\",\\"input_video_path\\":\\"' + input_video 101    input_json += '\\",\\"self_template_path\\":\\"' +template_root_path + self_template_path + '\\",'102    input_json += '\\"other_template_path\\":[' 103    for path in all_template:104        if path != self_template_path:105            input_json += '{\\"template_path\\":\\"'+template_root_path+path+'\\"},'106    input_json = input_json[0:len(input_json)-1]107    input_json += '],'108    input_json += '\\"video_info\\":[],'109    input_json +='\\"current_image_path\\":\\"' + template_root_path + self_template_path +'/xxx-blur.jpg\\",'110    input_json +='\\"unblurred_current_image_path\\":\\"' +template_root_path + self_template_path+ '/xxx.jpg\\"}"'111    112    return input_json113def runFaceRecog(input_json):114    args = face_recog_file +' '+ input_json115#    print args116    p = subprocess.Popen(args, stdin=subprocess.PIPE,  stdout=subprocess.PIPE,shell=True)117    out, err = p.communicate()118    return out, err119    120def handleResult(video , ouput_json, report, s, ms, mo):121   # print output_json122    data = json.loads(ouput_json)123    result = data["result"]124    error = data["message"]["error"]125    126    127    if result=="success":128        s +=1129        mode = data["message"]["mode"]130        match_self  = data["message"]["patient_self"]["is_match"]131    132        others = data["message"]["patient_other"]133        report.write(result + ' | ' + error + ' | ' + mode + ' | ' + match_self + ' | ' )134        match_other = "0"135        for other in others:136            if other["is_match"]!="0":137                match_other = "1"138        if match_self=="0" and mode != "enroll":139            print 'self not match'140            report.write(video +' | ' + ouput_json)141        else:142            ms += 1143            print '====self match======='144        if match_other=="0":145            print 'other not match'146        else:147            mo +=1148            print '===other match====='149            report.write(video +' | ' + ouput_json)150        report.write(match_other + '\n' )151    else:152        report.write(result + ' | ' + ouput_json + '\n' )153    return s,ms,mo154def process_main(config_xml):155    total = 0   156    MATCH_SELF = 0 157    MATCH_OTHER = 0 158    SUCCESS = 0 159    output_file = open(config_xml + out_file_name, 'w') 160    output_file.write('result  | error | mode | match_self | match_other\n' )161    for patient in patient_list:162        videolist = walk(video_root_path+patient)163        for video in videolist:164            input_json = prepareJson(video, patient,patient_list,config_xml )165            print input_json166            output_json, err = runFaceRecog(input_json)167            print output_json168            SUCCESS, MATCH_SELF, MATCH_OTHER = handleResult(video, output_json, output_file, SUCCESS, MATCH_SELF, MATCH_OTHER)169            total += 1170    output_file.write('SUMMARY:\n' )171    output_file.write('total#:'+str(total)+'\n' )172    output_file.write('success#:'+ str(SUCCESS)+'\n' )173    output_file.write('match_self#:' + str(MATCH_SELF)+'\n' )174    output_file.write('match_other#:' +str(MATCH_OTHER)+'\n' )175    output_file.close() 176    for patient in patient_list:177        clean(video_root_path+patient)178    179    180xmls = generate_param_xml()181for xml in xmls:...

Full Screen

Full Screen

token_matcher.js

Source:token_matcher.js Github

copy

Full Screen

...127      }128    }129    return false;130  };131  this.match_Other = function match_Other(token) {132    var text = token.line.getLineText(indentToRemove); //take the entire line, except removing DocString indents133    setTokenMatched(token, 'Other', unescapeDocString(text), null, 0);134    return true;135  };136  function matchTitleLine(token, tokenType, keywords) {137    var length = keywords.length;138    for(var i = 0, keyword; i < length; i++) {139      var keyword = keywords[i];140      if (token.line.startsWithTitleKeyword(keyword)) {141        var title = token.line.getRestTrimmed(keyword.length + ':'.length);142        setTokenMatched(token, tokenType, title, keyword);143        return true;144      }145    }...

Full Screen

Full Screen

main.py

Source:main.py Github

copy

Full Screen

1from collections import defaultdict2class Tile:3    def __init__(self, id_num, left, top, right, bottom):4        self.id_num = id_num5        self.front_edges = [left, top, right, bottom]6        self.back_edges = [right[::-1], top[::-1], left[::-1], bottom[::-1]]7        self.front_match_id =set()8        self.back_match_id =set()9        self.match_count = 010def p1():11    lines = []12    with open('input.txt') as fp:13        line = fp.readline()14        while line:15            line = line[:-1]16            lines.append(line)17            line = fp.readline()18    tiles = []19    for idx in range(0, len(lines), 12):20        id_num = int(lines[idx].split(' ')[1][:-1])21        left, right = '', ''22        for j in range(0, 10):23            left += lines[idx + 10 - j][0]24            right += lines[idx + 1 + j][9]25        top = lines[idx + 1]26        bottom = lines[idx + 10][::-1]27        tile = Tile(id_num, left, top, right, bottom)28        tiles.append(tile)29    for tile_a in tiles[:-1]:30        for tile_b in tiles[1:]:31            match_other = False32            # af vs bf33            for edge_a in tile_a.front_edges:34                if match_other:35                    break36                for edge_b in tile_b.front_edges:37                    edge_b = edge_b[::-1]38                    if edge_a == edge_b:39                        tile_a.match_count += 140                        tile_b.match_count += 141                        tile_a.front_match_id.add(f'{tile_b.id_num}.f')42                        tile_b.front_match_id.add(f'{tile_a.id_num}.f')43                        match_other = True44                        break45            # af vs bb46            for edge_a in tile_a.front_edges:47                if match_other:48                    break49                for edge_b in tile_b.back_edges:50                    edge_b = edge_b[::-1]51                    if edge_a == edge_b:52                        tile_a.match_count += 153                        tile_b.match_count += 154                        tile_a.front_match_id.add(f'{tile_b.id_num}.b')55                        tile_b.back_match_id.add(f'{tile_a.id_num}.f')56                        match_other = True57                        break58            # ab vs bf59            for edge_a in tile_a.back_edges:60                if match_other:61                    break62                for edge_b in tile_b.front_edges:63                    edge_b = edge_b[::-1]64                    if edge_a == edge_b:65                        tile_a.match_count += 166                        tile_b.match_count += 167                        tile_a.back_match_id.add(f'{tile_b.id_num}.f')68                        tile_b.front_match_id.add(f'{tile_a.id_num}.b')69                        match_other = True70                        break71            # ab vs bb72            for edge_a in tile_a.back_edges:73                if match_other:74                    break75                for edge_b in tile_b.back_edges:76                    edge_b = edge_b[::-1]77                    if edge_a == edge_b:78                        tile_a.match_count += 179                        tile_b.match_count += 180                        tile_a.back_match_id.add(f'{tile_b.id_num}.b')81                        tile_b.back_match_id.add(f'{tile_a.id_num}.b')82                        match_other = True83                        break84    match_counts = [t.match_count for t in tiles]85    print(1)86def p2():87    lines = []88    with open('input.txt') as fp:89        line = fp.readline()90        while line:91            line = line[:-1]92            lines.append(line)93            line = fp.readline()94if __name__ == '__main__':95    p1()...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var gherkin = require('cucumber-gherkin');2var match_Other = gherkin.match_Other;3var match_Step = gherkin.match_Step;4var match_Table = gherkin.match_Table;5var match_TableRow = gherkin.match_TableRow;6var match_Tag = gherkin.match_Tag;7var match_Tags = gherkin.match_Tags;8var match_Text = gherkin.match_Text;9var match_Then = gherkin.match_Then;10var match_When = gherkin.match_When;11var match_Given = gherkin.match_Given;12var match_And = gherkin.match_And;13var match_But = gherkin.match_But;14var match_Feature = gherkin.match_Feature;15var match_Background = gherkin.match_Background;16var match_Scenario = gherkin.match_Scenario;17var match_ScenarioOutline = gherkin.match_ScenarioOutline;18var match_Examples = gherkin.match_Examples;

Full Screen

Using AI Code Generation

copy

Full Screen

1var match_Other = require('cucumber-gherkin').match_Other;2var match_Other = require('cucumber-gherkin').match_Other;3var match_Other = require('cucumber-gherkin').match_Other;4var match_Other = require('cucumber-gherkin').match_Other;5var match_Other = require('cucumber-gherkin').match_Other;6var match_Other = require('cucumber-gherkin').match_Other;7var match_Other = require('cucumber-gherkin').match_Other;8var match_Other = require('cucumber-gherkin').match_Other;9var match_Other = require('cucumber-gherkin').match_Other;10var match_Other = require('cucumber-gherkin').match_Other;11var match_Other = require('cucumber-gherkin').match_Other;12var match_Other = require('cucumber-gherkin').match_Other;13var match_Other = require('cucumber-gherkin').match_Other;14var match_Other = require('cucumber-gherkin').match_Other;15var match_Other = require('cucumber-gherkin').match_Other;16var match_Other = require('cucumber-gherkin').match_Other;17var match_Other = require('cucumber-gherkin').match_Other;18var match_Other = require('cucumber-gherkin').match_Other;19var match_Other = require('cucumber-gherkin').match_Other;20var match_Other = require('cucumber-gherkin').match_Other;21var match_Other = require('cucumber-gherkin').match_Other;22var match_Other = require('cucumber-gherkin').match_Other;23var match_Other = require('cucumber-gherkin').match_Other;24var match_Other = require('cucumber-gherkin').match_Other;25var match_Other = require('cucumber-gherkin').match_Other;26var match_Other = require('cucumber-gherkin').match_Other;27var match_Other = require('cucumber-gherkin').match_Other;28var match_Other = require('cucumber-gher

Full Screen

Using AI Code Generation

copy

Full Screen

1var gherkin = require('cucumber-gherkin');2var fs = require('fs');3var file = fs.readFileSync('test.feature', 'utf8');4var match_Other = gherkin.match_Other;5var match = match_Other(file);6console.log(match);

Full Screen

Using AI Code Generation

copy

Full Screen

1var gherkin = require('cucumber-gherkin');2var fs = require('fs');3var gherkin_file = fs.readFileSync('test.feature', 'utf-8');4var gherkin_doc = gherkin.parse(gherkin_file);5var gherkin_doc_string = JSON.stringify(gherkin_doc);6var gherkin_doc_object = JSON.parse(gherkin_doc_string);7var match_other = gherkin.match_Other(gherkin_doc_object);8console.log(match_other);9[ { type: 'Step',10    line: 4 },11  { type: 'Step',12    line: 5 } ]

Full Screen

Using AI Code Generation

copy

Full Screen

1var gherkin = require('gherkin');2var parser = new gherkin.Parser();3var feature = parser.parse("Feature: test4");5var printer = new gherkin.AstTreePrinter();6printer.print(feature);7var gherkin = require('gherkin');8var parser = new gherkin.Parser();9var feature = parser.parse("Feature: test10");11var printer = new gherkin.AstTreePrinter();12printer.print(feature);13var gherkin = require('gherkin');14var parser = new gherkin.Parser();15var feature = parser.parse("Feature: test16");17var printer = new gherkin.AstTreePrinter();18printer.print(feature);19var gherkin = require('gherkin');20var parser = new gherkin.Parser();21var feature = parser.parse("Feature: test22");23var printer = new gherkin.AstTreePrinter();24printer.print(feature);25var gherkin = require('gherkin');26var parser = new gherkin.Parser();27var feature = parser.parse("Feature: test

Full Screen

Cucumber Tutorial:

LambdaTest offers a detailed Cucumber testing tutorial, explaining its features, importance, best practices, and more to help you get started with running your automation testing scripts.

Cucumber Tutorial Chapters:

Here are the detailed Cucumber testing chapters to help you get started:

  • Importance of Cucumber - Learn why Cucumber is important in Selenium automation testing during the development phase to identify bugs and errors.
  • Setting Up Cucumber in Eclipse and IntelliJ - Learn how to set up Cucumber in Eclipse and IntelliJ.
  • Running First Cucumber.js Test Script - After successfully setting up your Cucumber in Eclipse or IntelliJ, this chapter will help you get started with Selenium Cucumber testing in no time.
  • Annotations in Cucumber - To handle multiple feature files and the multiple scenarios in each file, you need to use functionality to execute these scenarios. This chapter will help you learn about a handful of Cucumber annotations ranging from tags, Cucumber hooks, and more to ease the maintenance of the framework.
  • Automation Testing With Cucumber And Nightwatch JS - Learn how to build a robust BDD framework setup for performing Selenium automation testing by integrating Cucumber into the Nightwatch.js framework.
  • Automation Testing With Selenium, Cucumber & TestNG - Learn how to perform Selenium automation testing by integrating Cucumber with the TestNG framework.
  • Integrate Cucumber With Jenkins - By using Cucumber with Jenkins integration, you can schedule test case executions remotely and take advantage of the benefits of Jenkins. Learn how to integrate Cucumber with Jenkins with this detailed chapter.
  • Cucumber Best Practices For Selenium Automation - Take a deep dive into the advanced use cases, such as creating a feature file, separating feature files, and more for Cucumber testing.

Run Cucumber-gherkin automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful