diff --git a/base_phone_rate/__manifest__.py b/base_phone_rate/__manifest__.py index f9c4095..965df4c 100644 --- a/base_phone_rate/__manifest__.py +++ b/base_phone_rate/__manifest__.py @@ -13,6 +13,7 @@ 'decimal_precision' ], 'data': [ + 'views/product_product.xml', 'views/phone_rate.xml', 'security/ir.model.access.csv', 'data/decimal_precision.xml', diff --git a/base_phone_rate/models/__init__.py b/base_phone_rate/models/__init__.py index 91ed7eb..5d2fef6 100644 --- a/base_phone_rate/models/__init__.py +++ b/base_phone_rate/models/__init__.py @@ -1 +1,2 @@ from . import phone_rate +from . import product_product diff --git a/base_phone_rate/models/phone_rate.py b/base_phone_rate/models/phone_rate.py index 34074ed..c18efcf 100644 --- a/base_phone_rate/models/phone_rate.py +++ b/base_phone_rate/models/phone_rate.py @@ -30,3 +30,14 @@ def _onchange_state_id(self): def _onchange_country_id(self): if self.state_id.country_id != self.country_id: self.state_id = False + + @api.model + def get_rate_from_phonenumber(self, phonenumber): + """Given a phone number, find the corresponding rate""" + for n in range(1, len(phonenumber)): + number = phonenumber[:n] + phonerate = self.search( + [('dial_prefix', '=ilike', number + '%')] + ) + if len(phonerate) == 1: + return phonerate diff --git a/base_phone_rate/models/product_product.py b/base_phone_rate/models/product_product.py new file mode 100644 index 0000000..dfcb8ff --- /dev/null +++ b/base_phone_rate/models/product_product.py @@ -0,0 +1,11 @@ +# Copyright 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductProduct(models.Model): + + _inherit = 'product.product' + + is_international_call = fields.Boolean() diff --git a/base_phone_rate/views/product_product.xml b/base_phone_rate/views/product_product.xml new file mode 100644 index 0000000..3ca7245 --- /dev/null +++ b/base_phone_rate/views/product_product.xml @@ -0,0 +1,17 @@ + + + + + + product.product.form (in base_phone_rate) + product.product + + + + + + + + + diff --git a/connector_equipment_import_cdr/README.rst b/connector_equipment_import_cdr/README.rst new file mode 100644 index 0000000..ac70910 --- /dev/null +++ b/connector_equipment_import_cdr/README.rst @@ -0,0 +1,84 @@ +============================== +Connector Equipment Import CDR +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fvertical--isp-lightgray.png?logo=github + :target: https://github.com/OCA/vertical-isp/tree/12.0/connector_equipment_import_cdr + :alt: OCA/vertical-isp +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/vertical-isp-12-0/vertical-isp-12-0-connector_equipment_import_cdr + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/158/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to import the CDR data from the Netsapiens backend as Odoo analytic lines. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Open Source Integrators + +Contributors +~~~~~~~~~~~~ + +* Serpent Consulting Services Pvt. Ltd. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-wolfhall| image:: https://github.com/wolfhall.png?size=40px + :target: https://github.com/wolfhall + :alt: wolfhall +.. |maintainer-max3903| image:: https://github.com/max3903.png?size=40px + :target: https://github.com/max3903 + :alt: max3903 + +Current `maintainers `__: + +|maintainer-wolfhall| |maintainer-max3903| + +This module is part of the `OCA/vertical-isp `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/connector_equipment_import_cdr/__init__.py b/connector_equipment_import_cdr/__init__.py new file mode 100644 index 0000000..69f7bab --- /dev/null +++ b/connector_equipment_import_cdr/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/connector_equipment_import_cdr/__manifest__.py b/connector_equipment_import_cdr/__manifest__.py new file mode 100644 index 0000000..5bbe4de --- /dev/null +++ b/connector_equipment_import_cdr/__manifest__.py @@ -0,0 +1,36 @@ +# Copyright (C) 2019, Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Connector Equipment Import CDR', + 'version': '12.0.1.0.0', + 'license': 'AGPL-3', + 'summary': 'This module allows to import the CDR data from the Netsapiens\ + backend as Odoo analytic lines.', + 'author': 'Open Source Integrators, Odoo Community Association (OCA)', + "website": 'https://github.com/OCA/vertical-isp', + 'depends': [ + 'account', + 'agreement_legal_sale', + 'agreement_maintenance', + 'base_phone_rate', + 'connector_equipment', + ], + 'data': [ + 'data/backend_equipment_cron.xml', + 'security/ir.model.access.csv', + 'views/agreement_serviceprofile.xml', + 'views/backend_equipment_productline.xml', + 'views/backend_view.xml', + ], + 'demo': [ + 'demo/product_demo.xml', + 'demo/backend_equipment_productline_demo.xml', + ], + 'installable': True, + 'development_status': 'Beta', + 'maintainers': [ + 'wolfhall', + 'max3903', + ], +} diff --git a/connector_equipment_import_cdr/data/backend_equipment_cron.xml b/connector_equipment_import_cdr/data/backend_equipment_cron.xml new file mode 100644 index 0000000..269c4a4 --- /dev/null +++ b/connector_equipment_import_cdr/data/backend_equipment_cron.xml @@ -0,0 +1,16 @@ + + + + Import CDR + + + + 1 + days + -1 + + code + model.import_cdr() + + + diff --git a/connector_equipment_import_cdr/demo/backend_equipment_productline_demo.xml b/connector_equipment_import_cdr/demo/backend_equipment_productline_demo.xml new file mode 100644 index 0000000..85eccd7 --- /dev/null +++ b/connector_equipment_import_cdr/demo/backend_equipment_productline_demo.xml @@ -0,0 +1,24 @@ + + + + 10 + + ** + + + + + 20 + + ** + + + + + 30 + + ** + + + + diff --git a/connector_equipment_import_cdr/demo/product_demo.xml b/connector_equipment_import_cdr/demo/product_demo.xml new file mode 100644 index 0000000..b655b02 --- /dev/null +++ b/connector_equipment_import_cdr/demo/product_demo.xml @@ -0,0 +1,66 @@ + + + + Minute(s) + + + smaller + + + + Type + 1 + + + + Domestic + + 1 + + + + International + + 2 + + + + Toll Free + + 3 + + + + Call + + service + True + True + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/connector_equipment_import_cdr/models/__init__.py b/connector_equipment_import_cdr/models/__init__.py new file mode 100644 index 0000000..4aff583 --- /dev/null +++ b/connector_equipment_import_cdr/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import backend_equipment_productline +from . import agreement_serviceprofile +from . import backend_equipment diff --git a/connector_equipment_import_cdr/models/agreement_serviceprofile.py b/connector_equipment_import_cdr/models/agreement_serviceprofile.py new file mode 100644 index 0000000..3a19074 --- /dev/null +++ b/connector_equipment_import_cdr/models/agreement_serviceprofile.py @@ -0,0 +1,107 @@ +# Copyright (C) 2019, Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import date, timedelta +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class AgreementServiceProfile(models.Model): + _inherit = 'agreement.serviceprofile' + + domain = fields.Char() + last_cdr_sync = fields.Date("Last CDR Sync Date") + + @api.multi + def get_billing_day(self): + self.ensure_one() + start_date = self.agreement_id.start_date + billing_date = start_date + timedelta(days=1) + return billing_date.day + + @api.multi + def import_cdr(self): + """ + Get call data for domain: type (in, out, ...), number, seconds + Filter only outbound calls, type==0 + Add billing Product, from number, using product map + And Group by Product + """ + def phone_to_product(outgoing_number, product_map): + for line in product_map: + product = line.product_id + match = outgoing_number == line.pattern # TODO apply RegEx + if match: + return product + return product + + phone_to_rate = self.env['phone.rate'].get_rate_from_phonenumber + # TODO loop should be by domain? + # We could be duplicating if same domain is in several service profiles + for service in self: + analytic = service.agreement_id.analytic_account_id.id + if not analytic: + raise UserError(_( + 'Analytic Account is not found in database.')) + # Group Data Lines by Product + backend = service.equipment_id.backend_id + product_map = backend.product_line_ids + domain_data = backend.api_get_cdr_data(service.domain) + products_data = {product: [] for pattern, product in product_map} + for line in domain_data: + call_type = { + '0': 'outbound', + '1': 'inbound', + '2': 'missed', + '3': 'on-net', + }.get(line['type']) + dialed_number = line['orig_req_user'] + # duration = line['duration'] + if call_type == 'outbound': + product = phone_to_product( + dialed_number, product_map) + if product.is_international_call: + line['phone_rate'] = phone_to_rate(dialed_number) + products_data[product].append(line) + # Create Analytic Line for each Product + AnalyticLine = self.env["account.analytic.line"] + for product, lines in products_data.items(): + calls = len(lines) + duration_secs = sum(x['duration'] for x in lines) + duration_mins = duration_secs // 60 # TODO rounding rule + start = min(x['time_start'] for x in lines) + end = max(x['time_release'] for x in lines) + amount = (product.is_international_call and sum( + x.get('phone_rate').rate + * int(x.get('duration', '0')) / 60.0 + for x in lines)) + uom = self.env.ref( # TODO Verify it is default data + 'connector_equipment_import_cdr.product_uom_min') + # billing_day = service.get_billling_day() + # TODO name = 'YYYYMM' + name = start + ' to ' + end + ref = "# Calls: %d" % (calls,) + + AnalyticLine.create({ + "name": name, + "account_id": analytic.id, + "date": date.today(), + "amount": amount or 0, + "ref": ref, + "partner_id": service.partner_id, + "unit_amount": duration_mins, + "product_id": product.id, + "product_uom_id": uom.id, + }) + service.last_cdr_sync = date.today() + + @api.multi + def button_import_cdr(self): + self.import_cdr() + + @api.model + def cron_import_cdr(self): + serviceprofiles = self.search( + [('domain', '!=', False), + ('equipment_id.backend_id', '!=', False)]) + serviceprofiles.import_cdr() diff --git a/connector_equipment_import_cdr/models/backend_equipment.py b/connector_equipment_import_cdr/models/backend_equipment.py new file mode 100644 index 0000000..31c4de1 --- /dev/null +++ b/connector_equipment_import_cdr/models/backend_equipment.py @@ -0,0 +1,89 @@ +# Copyright (C) 2019, Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from datetime import datetime, timedelta +import requests +import json + + +class BackendEquipment(models.Model): + _inherit = 'backend.equipment' + + product_line_ids = fields.One2many( + 'backend.equipment.product_line', + 'backend_id', string="Products") + sync_schedule = fields.Selection( + [('all_service', 'All Service Profiles Every Day')], 'Sync Schedule') + + @api.multi + def api_url_build(self, endpoint): + """ + Given an endpoint, + return full Netsapiens API URL for the endpoint + """ + self.ensure_one() + url = self.host + if not url.startswith('http'): + url = 'https://' + url + if url.endswith('/'): + url = url[:-1] + if self.port: + url = url + self.port + if endpoint: + url = url + endpoint + return url + + @api.multi + def api_generate_access_token(self): + """ + Login to Netsapiens and + return the access token + """ + self.ensure_one() + backend = self + token_value = { + 'grant_type': 'password', + 'client_id': backend.client, + 'client_secret': backend.client_secret, + 'username': backend.user, + 'password': backend.password, + } + url_path = self.api_url_build('/ns-api/oauth2/token/?') + request_data = requests.get( + url_path, + params=token_value, + headers={'Content-Type': 'application/json'}) + if request_data.status_code == 200: + request_data = json.loads(request_data.text) + token = request_data.get('access_token') + return token + # TODO else Raise an error if not successful? + + @api.model + def api_get_domain_cdr_data(self, service, from_date=None, to_date=None): + """ + Given a Service Profile, call the API to get CRD data. + Returns a dict with the retrieved data. + """ + backend = service.equipment_id.backend_id + # TODO convert dates to context timezone! + today_date = datetime.today() + yesterday = (today_date - timedelta(days=1)).date() + token = backend.api_generate_access_token() + token_value = { + 'object': 'cdr2', + 'action': 'read', + 'start_date': service.last_cdr_sync or '2000-01-01', + 'end_date': yesterday, + 'domain': service.domain, + 'format': 'json' + } + url_path = self._api_url_build('/ns-api/?') + domain_data = requests.post( + url_path, + params=token_value, + headers={'Authorization': 'Bearer' + ' ' + token}) + if domain_data.status_code == 200: + domain_data = json.loads(domain_data.text) + return domain_data diff --git a/connector_equipment_import_cdr/models/backend_equipment_productline.py b/connector_equipment_import_cdr/models/backend_equipment_productline.py new file mode 100644 index 0000000..7dc76e7 --- /dev/null +++ b/connector_equipment_import_cdr/models/backend_equipment_productline.py @@ -0,0 +1,17 @@ +# Copyright (C) 2019, Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class BackendEquipmentProductline(models.Model): + _name = 'backend.equipment.product_line' + _description = 'Backend Equipment Productline' + _order = "sequence" + + name = fields.Many2one('product.product', "Product") + pattern = fields.Char('Pattern', help="The pattern is a regular expression that\ + will applied on the phone number to detect a domestic call, an\ + international call or a toll free call.") + sequence = fields.Integer('Sequence') + backend_id = fields.Many2one('backend.equipment', "Backend") diff --git a/connector_equipment_import_cdr/readme/CONTRIBUTORS.rst b/connector_equipment_import_cdr/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..09b1864 --- /dev/null +++ b/connector_equipment_import_cdr/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Serpent Consulting Services Pvt. Ltd. diff --git a/connector_equipment_import_cdr/readme/DESCRIPTION.rst b/connector_equipment_import_cdr/readme/DESCRIPTION.rst new file mode 100644 index 0000000..15c53d4 --- /dev/null +++ b/connector_equipment_import_cdr/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to import the CDR data from the Netsapiens backend as Odoo analytic lines. diff --git a/connector_equipment_import_cdr/security/ir.model.access.csv b/connector_equipment_import_cdr/security/ir.model.access.csv new file mode 100644 index 0000000..1e03afd --- /dev/null +++ b/connector_equipment_import_cdr/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_backend_equipment_product_line,access_backend_equipment_product_line,model_backend_equipment_product_line,base.group_user,1,1,1,1 diff --git a/connector_equipment_import_cdr/static/description/icon.png b/connector_equipment_import_cdr/static/description/icon.png new file mode 100644 index 0000000..3a0328b Binary files /dev/null and b/connector_equipment_import_cdr/static/description/icon.png differ diff --git a/connector_equipment_import_cdr/static/description/index.html b/connector_equipment_import_cdr/static/description/index.html new file mode 100644 index 0000000..5aba44b --- /dev/null +++ b/connector_equipment_import_cdr/static/description/index.html @@ -0,0 +1,421 @@ + + + + + + +Connector Equipment Import CDR + + + +
+

Connector Equipment Import CDR

+ + +

Beta License: AGPL-3 OCA/vertical-isp Translate me on Weblate Try me on Runbot

+

This module allows to import the CDR data from the Netsapiens backend as Odoo analytic lines.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Open Source Integrators
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainers:

+

wolfhall max3903

+

This module is part of the OCA/vertical-isp project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/connector_equipment_import_cdr/views/agreement_serviceprofile.xml b/connector_equipment_import_cdr/views/agreement_serviceprofile.xml new file mode 100644 index 0000000..c148224 --- /dev/null +++ b/connector_equipment_import_cdr/views/agreement_serviceprofile.xml @@ -0,0 +1,18 @@ + + + + agreement.service.profile.inherited.view + agreement.serviceprofile + + + + + + + +