{"action":"created","comment":{"url":"https://api.github.com/repos/OCA/vertical-isp/pulls/comments/28593374","id":28593374,"diff_hunk":"@@ -204,140 +204,443 @@ class account_journal(orm.Model):\n     }\n \n \n+class contract_service(orm.Model):\n+    _inherit = 'contract.service'\n+\n+    def _get_operation_date(self, cr, uid, context):\n+        \"\"\" Get today's date in date object \"\"\"\n+        date = context.get(\"operation_date\")\n+        if not date:\n+            date = fields.date.context_today(self, cr, uid)\n+\n+        if not isinstance(date, datetime.date):\n+            date = datetime.datetime.strptime(\n+                date,\n+                DEFAULT_SERVER_DATE_FORMAT,\n+            ).date()\n+\n+        return date\n+\n+    def _get_invoice_day(self, cr, uid, context):\n+        res_company_obj = self.pool[\"res.company\"]\n+        res_company_data = res_company_obj.read(\n+            cr, uid,\n+            res_company_obj._company_default_get(cr, uid, context),\n+            context=context)\n+\n+        invoice_day = int(res_company_data['invoice_day'])\n+        return invoice_day\n+\n+    def _get_prorata_interval_rate(self, cr, uid, change_date, context=None):\n+        \"\"\" Get the prorata interval and price rate.\n+\n+        Returns a tuple (start_date, end_date, price percent)\n+        \"\"\"\n+        today = self._get_operation_date(cr, uid, context)\n+        invoice_day = self._get_invoice_day(cr, uid, context=context)\n+\n+        curmonth_days = calendar.monthrange(today.year,\n+                                            today.month)[1]\n+        month_days = calendar.monthrange(change_date.year,\n+                                         change_date.month)[1]\n+\n+        if invoice_day < today.day:\n+            start_date = add_months(change_date, 1)\n+            end_date = add_months(today, 1)\n+            end_date = end_date.replace(\n+                day=calendar.monthrange(end_date.year,\n+                                        end_date.month)[1],\n+            )\n+            month_days = calendar.monthrange(start_date.year,\n+                                             start_date.month)[1]\n+            ptx = self._prorata_rate(\n+                (month_days - start_date.day) + 1,\n+                month_days,\n+            )\n+            ptx += count_months_stupid(start_date, end_date)\n+\n+        else:  # if today_date <= invoice_day:\n+            if change_date < today.replace(day=1):\n+                # Activation in previous month\n+                start_date = add_months(change_date, 1)\n+                end_date = today.replace(day=curmonth_days)\n+                month_days = calendar.monthrange(start_date.year,\n+                                                 start_date.month)[1]\n+                ptx = self._prorata_rate(\n+                    (month_days - start_date.day) + 1,\n+                    month_days,\n+                )\n+                ptx += count_months_stupid(start_date, end_date)\n+            else:\n+                # Activation is in current month (or future - same)\n+                end_date = add_months(change_date, 1)\n+                start_date = end_date.replace(day=1)\n+                ptx = -1 * self._prorata_rate(\n+                    end_date.day,\n+                    calendar.monthrange(end_date.year,\n+                                        end_date.month)[1],\n+                )\n+\n+        return start_date, end_date, ptx\n+\n+    def _get_prorata_interval_rate_deactivate(self, cr, uid, change_date,\n+                                              context=None):\n+        today = self._get_operation_date(cr, uid, context)\n+        invoice_day = self._get_invoice_day(cr, uid, context)\n+\n+        if invoice_day < today.day:\n+            start_date = change_date\n+            end_date = add_months(today, 1)\n+            end_date = end_date.replace(\n+                day=calendar.monthrange(end_date.year,\n+                                        end_date.month)[1],\n+            )\n+\n+            month_days = calendar.monthrange(start_date.year,\n+                                             start_date.month)[1]\n+            ptx = -1 * self._prorata_rate(\n+                (month_days - start_date.day) + 1,\n+                month_days,\n+            )\n+            ptx -= count_months_stupid(start_date, end_date)\n+        else:  # if today <= invoice_day\n+            if change_date < today:\n+                # Deactivation in the past, refund period from\n+                # deactivation to end of current month\n+                start_date = change_date\n+                end_date = today.replace(\n+                    day=calendar.monthrange(today.year,\n+                                            today.month)[1],\n+                )\n+\n+                month_days = calendar.monthrange(start_date.year,\n+                                                 start_date.month)[1]\n+                ptx = -1 * self._prorata_rate(\n+                    (month_days - start_date.day) + 1,\n+                    month_days,\n+                )\n+                ptx -= count_months_stupid(start_date, end_date)\n+            else:\n+                # Deactivation in future, bill period from now to deactivation\n+                start_date = today\n+                end_date = change_date\n+                start_month_days = calendar.monthrange(start_date.year,\n+                                                       start_date.month)[1]\n+                end_month_days = calendar.monthrange(end_date.year,\n+                                                     end_date.month)[1]\n+                if add_months(start_date, 1).replace(day=1) > end_date:\n+                    # Both dates in same month\n+                    ptx = self._prorata_rate(\n+                        (end_date.day - start_date.day) + 1,\n+                        start_month_days,\n+                    )\n+                else:\n+                    ptx = sum((\n+                        # Rate for first month\n+                        self._prorata_rate(\n+                            (start_month_days - start_date.day) + 1,\n+                            start_month_days),\n+                        # Each full month excluding end month\n+                        count_months_stupid(start_date, end_date) - 1,\n+                        # Rate for end month\n+                        self._prorata_rate(end_date.day,\n+                                           end_month_days),\n+                    ))\n+\n+        return start_date, end_date, ptx\n+\n+\n class account_analytic_account(orm.Model):\n     _inherit = \"account.analytic.account\"\n \n     _columns = {\n         'close_date': fields.datetime('Close date'),\n-        'close_reason': fields.text('Reasons')\n+        'close_reason': fields.text('Reasons'),\n     }\n \n     _defaults = {\n         'close_date': fields.datetime.now\n     }\n \n-    def create_invoice(self, cr, uid, ids, prorata=False, context=None):\n-        return_int = False\n-        if isinstance(ids, int):\n-            return_int = True\n+    def get_last_invoice_date(self, cr, uid, ids, source_process, inv=False,\n+                              context=None):\n+        res = {}\n+        source_process = tuple((source_process or '').split(\",\"))\n+        account_ids = tuple(ids)\n+        if not ids:\n+            return res\n+\n+        cr.execute(\n+            \"\"\"\n+            SELECT account_analytic_line.account_id\n+                 , DATE(MAX(account_invoice.date_invoice))\n+            FROM account_analytic_line\n+            JOIN account_invoice\n+              ON account_analytic_line.invoice_id = account_invoice.id\n+            WHERE account_analytic_line.account_id IN %s\n+              AND account_analytic_line.invoice_id IS NOT NULL\n+              AND {neg} COALESCE(account_invoice.source_process, '') IN %s\n+            GROUP BY account_analytic_line.account_id\n+            \"\"\".format(neg=('NOT' if inv else '')),\n+            (account_ids, source_process),\n+        )\n+\n+        for account_id, lid in cr.fetchall():\n+            res[account_id] = lid\n+        return res\n+\n+    def get_last_invoice_date_non_prorata(self, cr, uid, ids, context=None):\n+        return self.get_last_invoice_date(cr, uid, ids,\n+                                          PROCESS_PRORATA, inv=True,\n+                                          context=context)\n+\n+    def send_email_contract_invoice(self, cr, uid, ids, context=None):\n+        context = context or {}\n+\n+        if not isinstance(ids, list):\n             ids = [ids]\n \n-        account_analytic_account_obj = self.pool.get('account.analytic.account')\n-        account_analytic_line = self.pool.get('account.analytic.line')\n-        contract_service_obj = self.pool.get('contract.service')\n-        res_company_obj = self.pool.get('res.company')\n         account_invoice_obj = self.pool.get('account.invoice')\n+        mail_template_obj = self.pool.get('email.template')\n+        ir_model_data_obj = self.pool.get('ir.model.data')\n+        mail_template_id = ir_model_data_obj.get_object_reference(\n+            cr, uid, 'account',\n+            'email_template_edi_invoice')[1]\n+        mail_mail_obj = self.pool.get('mail.mail')\n+\n+        for inv in ids:\n+            _logger.info(\n+                \"Mailing invoice %s\" % account_invoice_obj.browse(\n+                    cr, uid, inv, context=context).name)\n+\n+            try:\n+                mail_id = mail_template_obj.send_mail(\n+                    cr, uid, mail_template_id, inv, context=context)\n+                mail_message = mail_mail_obj.browse(\n+                    cr, uid, mail_id,\n+                    context=context).mail_message_id\n+                mail_message.write({'type': 'email'})\n+            except:\n+                _logger.error(\n+                    'Error generating mail for invoice %s:'\n+                    '\\n\\n%s' % (\n+                        account_invoice_obj.browse(\n+                            cr, uid, inv, context=context).name,\n+                        sys.exc_info()[0]))\n+\n+        return True\n+\n+    def _get_invoice_date(self, cr, uid, process, date=None, context=None):\n+        \"\"\"\n+        Get the date for an invoice, based on the billing process and the\n+        current date\n+\n+        Logic behind date parameters:\n+        invoice_day > billing day > cutoff day\n+\n+        Anything happening after cutoff day\n+\n+        Params:\n+        - process: source_process of the account.invoice to be created\n+        - date: date or datetime representing the date at which the invoice\n+                is being created. Defaults to context_today()\n+\n+        Returns:\n+        - date formatted as DEFAULT_SERVER_DATE_FORMAT\n+        \"\"\"\n+        if date is None:\n+            date = datetime.datetime.strptime(\n+                fields.date.context_today(self, cr, uid, context=context),\n+                DEFAULT_SERVER_DATE_FORMAT,\n+            ).date()\n+        res_company_obj = self.pool['res.company']\n         res_company_data = res_company_obj.read(\n             cr, uid,\n             res_company_obj._company_default_get(cr, uid, context),\n             context=context)\n-        wf_service = netsvc.LocalService(\"workflow\")\n \n-        if res_company_data['send_email_contract_invoice']:\n-            mail_template_obj = self.pool.get('email.template')\n-            ir_model_data_obj = self.pool.get('ir.model.data')\n-            mail_template_id = ir_model_data_obj.get_object_reference(\n-                cr, uid, 'account',\n-                'email_template_edi_invoice')[1]\n-            mail_mail_obj = self.pool.get('mail.mail')\n+        cutoff_day = res_company_data['cutoff_day']\n+        invoice_day = res_company_data['invoice_day']\n \n-        cuttoff_day = res_company_data['cutoff_day']\n+        cutoff_date = datetime.date(date.year, date.month, int(cutoff_day))\n+        invoice_date = date.replace(day=int(invoice_day))\n \n-        cutoff_date = datetime.date(\n-            datetime.date.today().year,\n-            datetime.date.today().month,\n-            int(cuttoff_day)\n-        )\n+        if process in (PROCESS_RECURRENT, PROCESS_PRORATA):\n+            if date >= cutoff_date:\n+                date = add_months(invoice_date, 1)\n+            else:\n+                date = invoice_date\n \n-        invoice_day = res_company_data['invoice_day']\n+        return date.strftime(DEFAULT_SERVER_DATE_FORMAT)\n+\n+    def _create_invoice(self, cr, uid, ids, context=None):\n+        context = context or {}\n \n-        invoice_date = datetime.date(\n-            datetime.date.today().year,\n-            datetime.date.today().month,\n-            int(invoice_day)\n+        data = {\n+            'name': True,\n+        }\n+\n+        if context.get('create_invoice_mode', 'contract') == 'reseller':\n+            data.update({'partner': True})\n+\n+        res = []\n+\n+        account_analytic_line_obj = self.pool['account.analytic.line']\n+        account_invoice_obj = self.pool['account.invoice']\n+        account_invoice_line_obj = self.pool['account.invoice.line']\n+        wf_service = netsvc.LocalService(\"workflow\")\n+\n+        if sum(-line.amount\n+               for line in account_analytic_line_obj.browse(\n+                   cr, uid, ids, context=context)\n+               ) < 0:\n+            for line in account_analytic_line_obj.browse(\n+                    cr, uid, ids, context=context):\n+                line.write({\"amount\": -line.amount})\n+            context[\"type\"] = \"out_refund\"\n+\n+        inv = account_analytic_line_obj.invoice_cost_create(\n+            cr, uid, ids, data=data, context=context)\n+\n+        if isinstance(inv, list):\n+            # vvinet - this could mess up refunds/invoices, does it get called?\n+            if len(inv) > 1:\n+                # Merge invoices\n+                query = [('invoice_id', 'in', inv)]\n+                line_ids = account_invoice_line_obj.search(\n+                    cr, uid, query, context=context)\n+                account_invoice_line_obj.write(\n+                    cr, uid, line_ids, {'invoice_id': inv[0]},\n+                    context=context)\n+                account_invoice_obj.button_compute(\n+                    cr, uid, [inv[0]], context=context)\n+                account_invoice_obj.unlink(cr, uid, inv[1:], context=context)\n+\n+            inv = inv[0]\n+\n+        # vvinet - any of the passed ids that did not get assigned to an\n+        #          invoice gets assigned to the first one\n+        unassigned_ids = account_invoice_line_obj.search(\n+            cr, uid, [('id', 'in', ids), ('invoice_id', '=', False)],\n         )\n+        if unassigned_ids:\n+            account_invoice_line_obj.write(cr, uid, {'invoice_id': inv})\n+\n+        res.append(inv)\n+\n+        # jgama - If its a prorata invoice, change the invoice date\n+        #         according to the invoice_day variable\n+        to_write = {}\n+        if context.get('source_process'):\n+            to_write['source_process'] = context['source_process']\n+        if context.get('date_invoice') and (\n+                context.get('prorata', False) or\n+                context.get('source_process') == PROCESS_RECURRENT):\n+            to_write['date_invoice'] = context.get('date_invoice')\n+\n+        if to_write:\n+            account_invoice_obj.write(cr, uid, inv, to_write, context=context)\n+\n+        if context.get('not_subscription_voucher', True):\n+            _logger.debug(\n+                \"Opening invoice %s\" % account_invoice_obj.browse(\n+                    cr, uid, inv, context=context).name)\n+            wf_service.trg_validate(\n+                uid, 'account.invoice', inv, 'invoice_open', cr)\n+\n+        return res\n+\n+    def create_invoice(self, cr, uid, ids, source_process=None, context=None):\n+        context = context or {}\n+        prorata = (source_process == PROCESS_PRORATA)\n+        _logger.debug(\"create_invoice %r %s\", ids, prorata)","path":"contract_isp_invoice/contract.py","position":572,"original_position":572,"commit_id":"7e058d21094ecf44cfb76ace8d441af5c910adc7","original_commit_id":"082f3691164cedac4affab2cf471e321425437e5","user":{"login":"gurneyalex","id":2032807,"avatar_url":"https://avatars.githubusercontent.com/u/2032807?v=3","gravatar_id":"","url":"https://api.github.com/users/gurneyalex","html_url":"https://github.com/gurneyalex","followers_url":"https://api.github.com/users/gurneyalex/followers","following_url":"https://api.github.com/users/gurneyalex/following{/other_user}","gists_url":"https://api.github.com/users/gurneyalex/gists{/gist_id}","starred_url":"https://api.github.com/users/gurneyalex/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gurneyalex/subscriptions","organizations_url":"https://api.github.com/users/gurneyalex/orgs","repos_url":"https://api.github.com/users/gurneyalex/repos","events_url":"https://api.github.com/users/gurneyalex/events{/privacy}","received_events_url":"https://api.github.com/users/gurneyalex/received_events","type":"User","site_admin":false},"body":"this is a correct logging call :wink:","created_at":"2015-04-17T13:30:08Z","updated_at":"2015-04-17T13:30:08Z","html_url":"https://github.com/OCA/vertical-isp/pull/4#discussion_r28593374","pull_request_url":"https://api.github.com/repos/OCA/vertical-isp/pulls/4","_links":{"self":{"href":"https://api.github.com/repos/OCA/vertical-isp/pulls/comments/28593374"},"html":{"href":"https://github.com/OCA/vertical-isp/pull/4#discussion_r28593374"},"pull_request":{"href":"https://api.github.com/repos/OCA/vertical-isp/pulls/4"}}},"pull_request":{"url":"https://api.github.com/repos/OCA/vertical-isp/pulls/4","id":33541264,"html_url":"https://github.com/OCA/vertical-isp/pull/4","diff_url":"https://github.com/OCA/vertical-isp/pull/4.diff","patch_url":"https://github.com/OCA/vertical-isp/pull/4.patch","issue_url":"https://api.github.com/repos/OCA/vertical-isp/issues/4","number":4,"state":"open","locked":false,"title":"Contract Invoicing Refactor","user":{"login":"veloutin","id":356233,"avatar_url":"https://avatars.githubusercontent.com/u/356233?v=3","gravatar_id":"","url":"https://api.github.com/users/veloutin","html_url":"https://github.com/veloutin","followers_url":"https://api.github.com/users/veloutin/followers","following_url":"https://api.github.com/users/veloutin/following{/other_user}","gists_url":"https://api.github.com/users/veloutin/gists{/gist_id}","starred_url":"https://api.github.com/users/veloutin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/veloutin/subscriptions","organizations_url":"https://api.github.com/users/veloutin/orgs","repos_url":"https://api.github.com/users/veloutin/repos","events_url":"https://api.github.com/users/veloutin/events{/privacy}","received_events_url":"https://api.github.com/users/veloutin/received_events","type":"User","site_admin":false},"body":"I know this is a pretty big diff... this comes from many months of adjustments, bug fixes and refactoring to allow extending in a few places. Now, notable changes:\r\n\r\n* Tests! Tests! Tests!\r\n* Allow creating analytic lines and invoicing them in a single call (useful for external processes that don't want to fail mid-transaction)\r\n* Fix fencepost error on partial month billing/refunds: billing Feb 1-28 would give you 27 days, etc..\r\n* Use the same logic as account.invoice to compute initial voucher amounts, avoiding mismatches in tax rounding between the voucher and invoice.\r\n* Add a few hooks here and there for extending","created_at":"2015-04-17T12:53:32Z","updated_at":"2015-04-17T13:30:08Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c67adf88c48276b7daa77be36f2d5a4e43b77a53","assignee":null,"milestone":{"url":"https://api.github.com/repos/OCA/vertical-isp/milestones/1","html_url":"https://github.com/OCA/vertical-isp/milestones/7.0","labels_url":"https://api.github.com/repos/OCA/vertical-isp/milestones/1/labels","id":1005832,"number":1,"title":"7.0","description":"","creator":{"login":"max3903","id":939952,"avatar_url":"https://avatars.githubusercontent.com/u/939952?v=3","gravatar_id":"","url":"https://api.github.com/users/max3903","html_url":"https://github.com/max3903","followers_url":"https://api.github.com/users/max3903/followers","following_url":"https://api.github.com/users/max3903/following{/other_user}","gists_url":"https://api.github.com/users/max3903/gists{/gist_id}","starred_url":"https://api.github.com/users/max3903/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/max3903/subscriptions","organizations_url":"https://api.github.com/users/max3903/orgs","repos_url":"https://api.github.com/users/max3903/repos","events_url":"https://api.github.com/users/max3903/events{/privacy}","received_events_url":"https://api.github.com/users/max3903/received_events","type":"User","site_admin":false},"open_issues":1,"closed_issues":1,"state":"open","created_at":"2015-03-04T14:40:20Z","updated_at":"2015-04-17T12:58:51Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/OCA/vertical-isp/pulls/4/commits","review_comments_url":"https://api.github.com/repos/OCA/vertical-isp/pulls/4/comments","review_comment_url":"https://api.github.com/repos/OCA/vertical-isp/pulls/comments{/number}","comments_url":"https://api.github.com/repos/OCA/vertical-isp/issues/4/comments","statuses_url":"https://api.github.com/repos/OCA/vertical-isp/statuses/7e058d21094ecf44cfb76ace8d441af5c910adc7","head":{"label":"savoirfairelinux:7.0_invoice_refactoring","ref":"7.0_invoice_refactoring","sha":"7e058d21094ecf44cfb76ace8d441af5c910adc7","user":{"login":"savoirfairelinux","id":2735545,"avatar_url":"https://avatars.githubusercontent.com/u/2735545?v=3","gravatar_id":"","url":"https://api.github.com/users/savoirfairelinux","html_url":"https://github.com/savoirfairelinux","followers_url":"https://api.github.com/users/savoirfairelinux/followers","following_url":"https://api.github.com/users/savoirfairelinux/following{/other_user}","gists_url":"https://api.github.com/users/savoirfairelinux/gists{/gist_id}","starred_url":"https://api.github.com/users/savoirfairelinux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/savoirfairelinux/subscriptions","organizations_url":"https://api.github.com/users/savoirfairelinux/orgs","repos_url":"https://api.github.com/users/savoirfairelinux/repos","events_url":"https://api.github.com/users/savoirfairelinux/events{/privacy}","received_events_url":"https://api.github.com/users/savoirfairelinux/received_events","type":"Organization","site_admin":false},"repo":{"id":27555394,"name":"vertical-isp","full_name":"savoirfairelinux/vertical-isp","owner":{"login":"savoirfairelinux","id":2735545,"avatar_url":"https://avatars.githubusercontent.com/u/2735545?v=3","gravatar_id":"","url":"https://api.github.com/users/savoirfairelinux","html_url":"https://github.com/savoirfairelinux","followers_url":"https://api.github.com/users/savoirfairelinux/followers","following_url":"https://api.github.com/users/savoirfairelinux/following{/other_user}","gists_url":"https://api.github.com/users/savoirfairelinux/gists{/gist_id}","starred_url":"https://api.github.com/users/savoirfairelinux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/savoirfairelinux/subscriptions","organizations_url":"https://api.github.com/users/savoirfairelinux/orgs","repos_url":"https://api.github.com/users/savoirfairelinux/repos","events_url":"https://api.github.com/users/savoirfairelinux/events{/privacy}","received_events_url":"https://api.github.com/users/savoirfairelinux/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/savoirfairelinux/vertical-isp","description":"Modules for Internet Service Providers","fork":true,"url":"https://api.github.com/repos/savoirfairelinux/vertical-isp","forks_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/forks","keys_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/keys{/key_id}","collaborators_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/teams","hooks_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/hooks","issue_events_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/issues/events{/number}","events_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/events","assignees_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/assignees{/user}","branches_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/branches{/branch}","tags_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/tags","blobs_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/git/refs{/sha}","trees_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/git/trees{/sha}","statuses_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/statuses/{sha}","languages_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/languages","stargazers_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/stargazers","contributors_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/contributors","subscribers_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/subscribers","subscription_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/subscription","commits_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/commits{/sha}","git_commits_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/git/commits{/sha}","comments_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/comments{/number}","issue_comment_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/issues/comments{/number}","contents_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/contents/{+path}","compare_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/compare/{base}...{head}","merges_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/merges","archive_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/downloads","issues_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/issues{/number}","pulls_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/pulls{/number}","milestones_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/milestones{/number}","notifications_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/labels{/name}","releases_url":"https://api.github.com/repos/savoirfairelinux/vertical-isp/releases{/id}","created_at":"2014-12-04T19:05:44Z","updated_at":"2014-12-04T19:05:44Z","pushed_at":"2015-04-17T13:21:06Z","git_url":"git://github.com/savoirfairelinux/vertical-isp.git","ssh_url":"git@github.com:savoirfairelinux/vertical-isp.git","clone_url":"https://github.com/savoirfairelinux/vertical-isp.git","svn_url":"https://github.com/savoirfairelinux/vertical-isp","homepage":"http://www.odoo-community.org/project/16","size":434,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"OCA:7.0","ref":"7.0","sha":"340bbfe0af7fe03853ea6c55f11f7115987d1629","user":{"login":"OCA","id":7600578,"avatar_url":"https://avatars.githubusercontent.com/u/7600578?v=3","gravatar_id":"","url":"https://api.github.com/users/OCA","html_url":"https://github.com/OCA","followers_url":"https://api.github.com/users/OCA/followers","following_url":"https://api.github.com/users/OCA/following{/other_user}","gists_url":"https://api.github.com/users/OCA/gists{/gist_id}","starred_url":"https://api.github.com/users/OCA/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OCA/subscriptions","organizations_url":"https://api.github.com/users/OCA/orgs","repos_url":"https://api.github.com/users/OCA/repos","events_url":"https://api.github.com/users/OCA/events{/privacy}","received_events_url":"https://api.github.com/users/OCA/received_events","type":"Organization","site_admin":false},"repo":{"id":21203622,"name":"vertical-isp","full_name":"OCA/vertical-isp","owner":{"login":"OCA","id":7600578,"avatar_url":"https://avatars.githubusercontent.com/u/7600578?v=3","gravatar_id":"","url":"https://api.github.com/users/OCA","html_url":"https://github.com/OCA","followers_url":"https://api.github.com/users/OCA/followers","following_url":"https://api.github.com/users/OCA/following{/other_user}","gists_url":"https://api.github.com/users/OCA/gists{/gist_id}","starred_url":"https://api.github.com/users/OCA/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OCA/subscriptions","organizations_url":"https://api.github.com/users/OCA/orgs","repos_url":"https://api.github.com/users/OCA/repos","events_url":"https://api.github.com/users/OCA/events{/privacy}","received_events_url":"https://api.github.com/users/OCA/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/OCA/vertical-isp","description":"Odoo Modules for Internet Service Providers","fork":false,"url":"https://api.github.com/repos/OCA/vertical-isp","forks_url":"https://api.github.com/repos/OCA/vertical-isp/forks","keys_url":"https://api.github.com/repos/OCA/vertical-isp/keys{/key_id}","collaborators_url":"https://api.github.com/repos/OCA/vertical-isp/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/OCA/vertical-isp/teams","hooks_url":"https://api.github.com/repos/OCA/vertical-isp/hooks","issue_events_url":"https://api.github.com/repos/OCA/vertical-isp/issues/events{/number}","events_url":"https://api.github.com/repos/OCA/vertical-isp/events","assignees_url":"https://api.github.com/repos/OCA/vertical-isp/assignees{/user}","branches_url":"https://api.github.com/repos/OCA/vertical-isp/branches{/branch}","tags_url":"https://api.github.com/repos/OCA/vertical-isp/tags","blobs_url":"https://api.github.com/repos/OCA/vertical-isp/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/OCA/vertical-isp/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/OCA/vertical-isp/git/refs{/sha}","trees_url":"https://api.github.com/repos/OCA/vertical-isp/git/trees{/sha}","statuses_url":"https://api.github.com/repos/OCA/vertical-isp/statuses/{sha}","languages_url":"https://api.github.com/repos/OCA/vertical-isp/languages","stargazers_url":"https://api.github.com/repos/OCA/vertical-isp/stargazers","contributors_url":"https://api.github.com/repos/OCA/vertical-isp/contributors","subscribers_url":"https://api.github.com/repos/OCA/vertical-isp/subscribers","subscription_url":"https://api.github.com/repos/OCA/vertical-isp/subscription","commits_url":"https://api.github.com/repos/OCA/vertical-isp/commits{/sha}","git_commits_url":"https://api.github.com/repos/OCA/vertical-isp/git/commits{/sha}","comments_url":"https://api.github.com/repos/OCA/vertical-isp/comments{/number}","issue_comment_url":"https://api.github.com/repos/OCA/vertical-isp/issues/comments{/number}","contents_url":"https://api.github.com/repos/OCA/vertical-isp/contents/{+path}","compare_url":"https://api.github.com/repos/OCA/vertical-isp/compare/{base}...{head}","merges_url":"https://api.github.com/repos/OCA/vertical-isp/merges","archive_url":"https://api.github.com/repos/OCA/vertical-isp/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/OCA/vertical-isp/downloads","issues_url":"https://api.github.com/repos/OCA/vertical-isp/issues{/number}","pulls_url":"https://api.github.com/repos/OCA/vertical-isp/pulls{/number}","milestones_url":"https://api.github.com/repos/OCA/vertical-isp/milestones{/number}","notifications_url":"https://api.github.com/repos/OCA/vertical-isp/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/OCA/vertical-isp/labels{/name}","releases_url":"https://api.github.com/repos/OCA/vertical-isp/releases{/id}","created_at":"2014-06-25T13:11:44Z","updated_at":"2015-03-27T18:17:45Z","pushed_at":"2015-04-17T13:21:07Z","git_url":"git://github.com/OCA/vertical-isp.git","ssh_url":"git@github.com:OCA/vertical-isp.git","clone_url":"https://github.com/OCA/vertical-isp.git","svn_url":"https://github.com/OCA/vertical-isp","homepage":"http://www.odoo-community.org/project/16","size":424,"stargazers_count":3,"watchers_count":3,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":9,"mirror_url":null,"open_issues_count":3,"forks":9,"open_issues":3,"watchers":3,"default_branch":"8.0"}},"_links":{"self":{"href":"https://api.github.com/repos/OCA/vertical-isp/pulls/4"},"html":{"href":"https://github.com/OCA/vertical-isp/pull/4"},"issue":{"href":"https://api.github.com/repos/OCA/vertical-isp/issues/4"},"comments":{"href":"https://api.github.com/repos/OCA/vertical-isp/issues/4/comments"},"review_comments":{"href":"https://api.github.com/repos/OCA/vertical-isp/pulls/4/comments"},"review_comment":{"href":"https://api.github.com/repos/OCA/vertical-isp/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/OCA/vertical-isp/pulls/4/commits"},"statuses":{"href":"https://api.github.com/repos/OCA/vertical-isp/statuses/7e058d21094ecf44cfb76ace8d441af5c910adc7"}}}}