diff --git a/connector_carepoint/consumer.py b/connector_carepoint/consumer.py index f6184b7..7570357 100644 --- a/connector_carepoint/consumer.py +++ b/connector_carepoint/consumer.py @@ -3,14 +3,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # from openerp.addons.connector.connector import Binder -from .unit.export_synchronizer import (export_record, - ) +from .unit.export_synchronizer import export_record # from .unit.delete_synchronizer import export_delete_record # from .connector import get_environment -# from openerp.addons.connector.event import (on_record_write, -# on_record_create, -# on_record_unlink -# ) +from openerp.addons.connector.event import (on_record_write, + on_record_create, + # on_record_unlink + ) import logging @@ -21,10 +20,6 @@ # 'carepoint.carepoint.address', # 'carepoint.carepoint.address.patient', # ]) -# @on_record_write(model_names=['carepoint.medical.patient', -# 'carepoint.carepoint.address', -# 'carepoint.carepoint.address.patient', -# ]) def delay_export(session, model_name, record_id, vals): """ Delay a job which export a binding record. (A binding record being a ``carepoint.res.partner``, @@ -36,10 +31,8 @@ def delay_export(session, model_name, record_id, vals): export_record.delay(session, model_name, record_id, fields=fields) -# @on_record_write(model_names=['medical.patient', -# 'carepoint.address', -# 'carepoint.address.patient', -# ]) +@on_record_write(model_names=['carepoint.address', + ]) def delay_export_all_bindings(session, model_name, record_id, vals): """ Delay a job which export all the bindings of a record. In this case, it is called on records of normal models and will delay @@ -54,6 +47,23 @@ def delay_export_all_bindings(session, model_name, record_id, vals): fields=fields) +@on_record_create(model_names=['carepoint.address', + ]) +def delay_create(session, model_name, record_id, vals): + """ Create a new binding record, then trigger delayed export + In this case, it is called on records of normal models to create + binding record, and trigger external system export + """ + model_obj = session.env['carepoint.%s' % model_name].with_context( + connector_no_export=True, + ) + if not len(model_obj.search([('odoo_id', '=', record_id)])): + model_obj.create({ + 'odoo_id': record_id, + }) + delay_export_all_bindings(session, model_name, record_id, vals) + + # @on_record_unlink(model_names=['carepoint.medical.patient', # 'carepoint.carepoint.address', # 'carepoint.carepoint.address.patient', diff --git a/connector_carepoint/models/address_abstract.py b/connector_carepoint/models/address_abstract.py index be240cb..944d3f1 100644 --- a/connector_carepoint/models/address_abstract.py +++ b/connector_carepoint/models/address_abstract.py @@ -3,13 +3,15 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging -from openerp import models, fields, api +from openerp import models, fields, api, _ from openerp.addons.connector.unit.mapper import (mapping, only_create, + ExportMapper, ) from ..unit.mapper import CarepointImportMapper from ..backend import carepoint from ..unit.import_synchronizer import CarepointImporter +from ..unit.export_synchronizer import CarepointExporter from .address import CarepointAddress @@ -37,11 +39,35 @@ class CarepointAddressAbstract(models.AbstractModel): string='Partner', comodel_name='res.partner', required=True, + store=True, compute='_compute_partner_id', inverse='_set_partner_id', ) + res_model = fields.Char( + string='Resource Model', + default=lambda s: s._default_res_model(), + ) + res_id = fields.Integer( + string='Resource PK', + compute='_compute_res_id', + store=True, + ) + + @property + @api.multi + def medical_entity_id(self): + self.ensure_one() + return self.env[self.res_model].browse(self.res_id) + + @api.model + def _default_res_model(self): + """ It returns the res model. Should be overloaded in children """ + raise NotImplementedError( + _('_default_res_model should be implemented in child classes') + ) @api.multi + @api.depends('partner_id') def _compute_partner_id(self): """ It sets the partner_id from the address_id """ for rec_id in self: @@ -71,6 +97,44 @@ def _set_partner_id(self): else: rec_id.address_id.write(vals) + @api.multi + @api.depends('partner_id', 'res_model') + def _compute_res_id(self): + """ It computes the resource ID from model """ + for rec_id in self: + if not all([rec_id.res_model, rec_id.partner_id]): + continue + medical_entity = self.env[rec_id.res_model].search([ + ('partner_id', '=', rec_id.partner_id.id), + ], + limit=1, + ) + rec_id.res_id = medical_entity.id + + @api.model + def _get_by_partner(self, partner, create=True, recurse=False): + """ It returns the address associated to the partner. + Params: + partner: Recordset singleton of partner to search for + create: Bool determining whether to create address if not exist + recurse: Bool determining whether to recurse into children (this + is only really useful when create=True) + Return: + Recordset of partner address + """ + address = self.search([('partner_id', '=', partner.id)], limit=1) + vals = self.address_id._get_partner_sync_vals(partner) + _logger.info('_get_by_partner %s, %s, %s' % (address, partner, vals)) + if not address and create: + vals['partner_id'] = partner.id + address = self.create(vals) + else: + address.write(vals) + if recurse: + for child in partner.child_ids: + self._get_by_partner(child, create, recurse) + return address + @carepoint class CarepointAddressAbstractImportMapper(CarepointImportMapper): @@ -116,6 +180,19 @@ def partner_id(self, record, medical_entity): partner = self.env['res.partner'].create(vals) return {'partner_id': partner.id} + def res_model_and_id(self, record, medical_entity): + """ It returns the vals dict for res_model and res_id + Params: + record: ``dict`` of carepoint record + medical_entity: Recordset with a ``partner_id`` column + Return: + ``dict`` of values for mapping + """ + return { + 'res_id': medical_entity.id, + 'res_model': medical_entity._name, + } + @mapping @only_create def address_id(self, record): @@ -139,3 +216,38 @@ def _create(self, data): self.session, binding._name, binding.id, binding.backend_id.id ) return binding + + +@carepoint +class CarepointAddressAbstractExportMapper(ExportMapper): + + @mapping + def addr_id(self, binding): + binder = self.binder_for('carepoint.carepoint.address') + rec_id = binder.to_backend(binding.address_id.id) + return {'addr_id': rec_id} + + @mapping + def static_defaults(self, binding): + return { + 'priority': 2, + 'addr_type_cn': 2, + 'app_flags': 0, + } + + +@carepoint +class CarepointAddressAbstractExporter(CarepointExporter): + + @mapping + def _export_dependencies(self): + model = self._model_name + if isinstance(model, list): + model = model[0] + _logger.debug( + _('_model_name was a list, using %s from %s') % ( + self._model_name, model, + ) + ) + self._export_dependency(self.binding_record.address_id, + model) diff --git a/connector_carepoint/tests/models/test_address_abstract.py b/connector_carepoint/tests/models/test_address_abstract.py index 2d3547c..a26d56a 100644 --- a/connector_carepoint/tests/models/test_address_abstract.py +++ b/connector_carepoint/tests/models/test_address_abstract.py @@ -57,11 +57,12 @@ def new_address(self, partner=None): return self.env['carepoint.address'].create(vals) def new_patient_address(self): - patient = self.new_patient() - self.address = self.new_address(patient.partner_id) + self.patient = self.new_patient() + self.address = self.new_address(self.patient.partner_id) return self.model.create({ - 'partner_id': patient.partner_id, + 'partner_id': self.patient.partner_id.id, 'address_id': self.address.id, + 'res_model': 'medical.patient', }) def test_compute_partner_id(self): @@ -94,6 +95,21 @@ def test_set_partner_id_with_address(self): address.street, ) + def test_medical_entity_id(self): + """ It should return patient record """ + address = self.new_patient_address() + self.assertEqual( + self.patient, + address.medical_entity_id, + ) + + def test_compute_res_id(self): + address = self.new_patient_address() + self.assertEqual( + self.patient.id, + address.res_id, + ) + class TestAddressAbstractImportMapper(AddressAbstractTestBase): @@ -172,6 +188,16 @@ def test_address_id_return(self): expect = self.unit.binder_for().to_odoo() self.assertDictEqual({'address_id': expect}, res) + def test_res_model_and_id(self): + """ It should return values dict for medical entity """ + entity = mock.MagicMock() + expect = { + 'res_id': entity.id, + 'res_model': entity._name, + } + res = self.unit.res_model_and_id(None, entity) + self.assertDictEqual(expect, res) + class TestAddressAbstractImporter(AddressAbstractTestBase): @@ -191,3 +217,79 @@ def test_import_dependencies(self): 'carepoint.carepoint.address', ), ]) + + +class TestAddressAbstractExportMapper(AddressAbstractTestBase): + + def setUp(self): + super(TestAddressAbstractExportMapper, self).setUp() + self.Unit = address_abstract.CarepointAddressAbstractExportMapper + self.unit = self.Unit(self.mock_env) + self.record = mock.MagicMock() + + def test_addr_id_get_binder(self): + """ It should get binder for prescription line """ + with mock.patch.object(self.unit, 'binder_for'): + self.unit.binder_for.side_effect = EndTestException + with self.assertRaises(EndTestException): + self.unit.addr_id(self.record) + self.unit.binder_for.assert_called_once_with( + 'carepoint.carepoint.address' + ) + + def test_addr_id_to_backend(self): + """ It should get backend record for rx """ + with mock.patch.object(self.unit, 'binder_for'): + self.unit.binder_for().to_backend.side_effect = EndTestException + with self.assertRaises(EndTestException): + self.unit.addr_id(self.record) + self.unit.binder_for().to_backend.assert_called_once_with( + self.record.address_id.id, + ) + + def test_addr_id_return(self): + """ It should return formatted addr_id """ + with mock.patch.object(self.unit, 'binder_for'): + res = self.unit.addr_id(self.record) + expect = self.unit.binder_for().to_backend() + self.assertDictEqual({'addr_id': expect}, res) + + def test_static_defaults(self): + """ It should return a dict of default values """ + self.assertIsInstance( + self.unit.static_defaults(self.record), + dict, + ) + + +class TestAddressAbstractExporter(AddressAbstractTestBase): + + def setUp(self): + super(TestAddressAbstractExporter, self).setUp() + self.Unit = address_abstract.CarepointAddressAbstractExporter + self.unit = self.Unit(self.mock_env) + self.record = mock.MagicMock() + self.unit.binding_record = self.record + + def test_export_dependencies_export(self): + """ It should export all dependencies """ + with mock.patch.object(self.unit, '_export_dependency') as mk: + self.unit._export_dependencies() + mk.assert_has_calls([ + mock.call( + self.record.address_id, + self.unit._model_name, + ), + ]) + + def test_export_dependencies_list(self): + """ It should correctly handle list _model_name as list """ + with mock.patch.object(self.unit, '_export_dependency') as mk: + self.unit._model_name = ['expect'] + self.unit._export_dependencies() + mk.assert_has_calls([ + mock.call( + self.record.address_id, + self.unit._model_name[0], + ), + ]) diff --git a/connector_carepoint/tests/test_backend_adapter.py b/connector_carepoint/tests/test_backend_adapter.py index 5833438..3e3626f 100644 --- a/connector_carepoint/tests/test_backend_adapter.py +++ b/connector_carepoint/tests/test_backend_adapter.py @@ -2,6 +2,8 @@ # Copyright 2015-2016 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import mock + from openerp.addons.connector_carepoint.unit import backend_adapter from .common import SetUpCarepointBase @@ -194,13 +196,41 @@ def test_create_creates(self): expect, ) - def test_create_returns_result(self): - """ It should return newly created record """ + def test_create_gets_pks(self): + """ It should get primary keys of model """ + with self.mock_api() as api: + model = self._init_model() + model.create({'data': 'test', 'col': 12323423}) + api().get_pks.assert_called_once_with( + api()[self.api_camel], + ) + + def test_create_gets_sequences(self): + """ It should get next sequence for PK """ + expect = mock.MagicMock() + with self.mock_api() as api: + model = self._init_model() + api().get_pks.return_value = [expect] + model.create({'data': 'test', 'col': 12323423}) + api().get_next_sequence.assert_called_once_with( + expect, + ) + + def test_create_returns_pks(self): + """ It should return comma joined PKs of new record """ + expect = ['col', 'no_exist'] with self.mock_api() as api: - res = self._init_model().create( - {'data': 'test', 'col': 12323423} + model = self._init_model() + api().get_pks.return_value = expect + expect = {'data': 'test', 'col': 12323423} + res = model.create(expect) + self.assertEqual( + '%s,%s' % ( + str(expect['col']), + api().get_next_sequence(), + ), + res, ) - self.assertEqual(api().create(), res) def test_delete_deletes(self): """ It should delete w/ proper vals """ @@ -218,14 +248,12 @@ def test_delete_returns_result(self): res = self._init_model().delete(123) self.assertEqual(api().delete(), res) - def test_write_updates(self): - """ It should update w/ proper vals """ + def test_write_gets_session(self): + """ It should get session for model """ with self.mock_api() as api: - expect = [123, {'data': 'test', 'col': 12323423}] - self._init_model().write(*expect) - api().update.assert_called_once_with( + self._init_model().write(None, None) + api()._get_session.assert_called_once_with( api()[self.api_camel], - *expect ) def test_write_returns_result(self): @@ -234,4 +262,7 @@ def test_write_returns_result(self): res = self._init_model().write( 123, {'data': 'test', 'col': 12323423}, ) - self.assertEqual(api().update(), res) + self.assertEqual( + api()._do_queries(), + res, + ) diff --git a/connector_carepoint/tests/test_base_exporter.py b/connector_carepoint/tests/test_base_exporter.py index 535b40c..9977c69 100644 --- a/connector_carepoint/tests/test_base_exporter.py +++ b/connector_carepoint/tests/test_base_exporter.py @@ -38,14 +38,18 @@ def _new_exporter(self, carepoint_id=None, binding_record=None, exporter.carepoint_id = carepoint_id exporter.binding_record = binding_record exporter.binding_id = binding_id + self.exporter = exporter return exporter def _new_record(self, sync_date=False): - return self.env[self.model].create({ + rec = self.env[self.model].create({ 'name': 'Test', 'sync_date': sync_date, 'warehouse_id': self.env.ref('stock.warehouse0').id, + 'carepoint_id': self.carepoint_id, }) + self.binding_id = rec.id + return rec def test_exporter_init_binding_id(self): """ It should init binding_id as None """ @@ -192,18 +196,6 @@ def test_run_sets_binding_id(self): self.binding_id, exporter.binding_id, ) - def test_run_gets_backend(self): - """ It should get the backend for binding """ - exporter = self._new_exporter( - carepoint_id=self.carepoint_id, - binding_record=self._new_record('2016-06-12 00:00:00'), - ) - with mock.patch.object(exporter.binder, 'to_backend') as mk: - mk.side_effect = EndTestException - with self.assertRaises(EndTestException): - exporter.run(self.binding_id) - mk.assert_called_once_with(self.binding_id) - def test_run_should_import(self): """ It should see if the record needs to be imported """ exporter = self._new_exporter( @@ -274,7 +266,7 @@ def test_run_calls_bind(self): with self.assertRaises(EndTestException): exporter.run(self.binding_id) binder.bind.assert_called_once_with( - binder.to_backend(), self.binding_id, + self.carepoint_id, self.binding_id, ) def test_run_commits_session(self): diff --git a/connector_carepoint/unit/backend_adapter.py b/connector_carepoint/unit/backend_adapter.py index e5ee2bf..dd10bf3 100644 --- a/connector_carepoint/unit/backend_adapter.py +++ b/connector_carepoint/unit/backend_adapter.py @@ -113,12 +113,20 @@ def search_read(self, attributes=None, **filters): def create(self, data): """ Wrapper to create a record on the external system - :param data: Data to create record with - :type data: dict - :rtype: :class:`sqlalchemy.ext.declarative.Declarative` + Params: + data: ``dict`` of Data to create record with + Returns: + ``str`` of external carepoint_id """ model_obj = self.__get_cp_model() - return self.carepoint.create(model_obj, data) + pks = self.carepoint.get_pks(model_obj) + out_pks = [] + for pk in pks: + if not data.get(pk): + data[pk] = self.carepoint.get_next_sequence(pk) + out_pks.append(str(data[pk])) + self.carepoint.create(model_obj, data) + return ','.join(out_pks) def write(self, _id, data): """ Update record on the external system @@ -126,10 +134,18 @@ def write(self, _id, data): :type _id: int :param data: Data to create record with :type data: dict - :rtype: :class:`sqlalchemy.ext.declarative.Declarative` + :rtype: :class:`sqlalchemy.engine.ResultProxy` """ model_obj = self.__get_cp_model() - return self.carepoint.update(model_obj, _id, data) + session = self.carepoint._get_session(model_obj) + + def __update(): + record = self.read(_id) + for key, val in data.iteritems(): + setattr(record, key, val) + return record + + return self.carepoint._do_queries(session, __update) def delete(self, _id): """ Delete record on the external system diff --git a/connector_carepoint/unit/export_synchronizer.py b/connector_carepoint/unit/export_synchronizer.py index a173c4d..6a7141c 100644 --- a/connector_carepoint/unit/export_synchronizer.py +++ b/connector_carepoint/unit/export_synchronizer.py @@ -88,7 +88,7 @@ def run(self, binding_id, *args, **kwargs): self.binding_id = binding_id self.binding_record = self._get_odoo_data() - self.carepoint_id = self.binder.to_backend(self.binding_id) + self.carepoint_id = self.binding_record.carepoint_id try: should_import = self._should_import()