diff --git a/.DS_Store b/.DS_Store index 3ed1ee65..857b631d 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/fusion_clock/controllers/__pycache__/clock_kiosk.cpython-312.pyc b/fusion_clock/controllers/__pycache__/clock_kiosk.cpython-312.pyc index 29a88a21..c1abbf6c 100644 Binary files a/fusion_clock/controllers/__pycache__/clock_kiosk.cpython-312.pyc and b/fusion_clock/controllers/__pycache__/clock_kiosk.cpython-312.pyc differ diff --git a/fusion_clock/controllers/__pycache__/clock_nfc_kiosk.cpython-312.pyc b/fusion_clock/controllers/__pycache__/clock_nfc_kiosk.cpython-312.pyc index 9400206e..3c9f003f 100644 Binary files a/fusion_clock/controllers/__pycache__/clock_nfc_kiosk.cpython-312.pyc and b/fusion_clock/controllers/__pycache__/clock_nfc_kiosk.cpython-312.pyc differ diff --git a/fusion_clock/models/__pycache__/hr_attendance.cpython-312.pyc b/fusion_clock/models/__pycache__/hr_attendance.cpython-312.pyc index 99ad6695..21d79813 100644 Binary files a/fusion_clock/models/__pycache__/hr_attendance.cpython-312.pyc and b/fusion_clock/models/__pycache__/hr_attendance.cpython-312.pyc differ diff --git a/fusion_clock/models/__pycache__/res_config_settings.cpython-312.pyc b/fusion_clock/models/__pycache__/res_config_settings.cpython-312.pyc index 0e699f1a..46c57b4b 100644 Binary files a/fusion_clock/models/__pycache__/res_config_settings.cpython-312.pyc and b/fusion_clock/models/__pycache__/res_config_settings.cpython-312.pyc differ diff --git a/fusion_inventory/__manifest__.py b/fusion_inventory/__manifest__.py index 94ea61f1..b6764a12 100644 --- a/fusion_inventory/__manifest__.py +++ b/fusion_inventory/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'Fusion Inventory', - 'version': '19.0.1.0.0', + 'version': '19.0.1.1.0', 'category': 'Inventory', 'summary': 'Advanced inventory management with margin tracking, sync, portal sheet, and inter-company transfers', 'description': """ diff --git a/fusion_inventory/models/sync_config.py b/fusion_inventory/models/sync_config.py index 6d737c78..c01d8efa 100644 --- a/fusion_inventory/models/sync_config.py +++ b/fusion_inventory/models/sync_config.py @@ -72,15 +72,24 @@ class FusionSyncConfig(models.Model): # ── XML-RPC Connection ── - def _get_xmlrpc_connection(self): + def _get_xmlrpc_connection(self, force=False): self.ensure_one() url = self.url.rstrip('/') try: + models_proxy = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object', allow_none=True) + # Reuse the cached uid to skip the authenticate() handshake — each + # authenticate writes a login-audit row on the remote. execute_kw + # still re-checks the API key on every call, so this stays secure. + # force=True forces a fresh authenticate (Test Connection / manual + # inter-company ops, which must work even if the cache went stale). + if self.remote_uid and not force: + return self.remote_uid, models_proxy common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common', allow_none=True) uid = common.authenticate(self.db_name, self.username, self.api_key, {}) if not uid: raise UserError('Authentication failed. Check username/API key.') - models_proxy = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object', allow_none=True) + if uid != self.remote_uid: + self.sudo().write({'remote_uid': uid}) return uid, models_proxy except xmlrpc.client.Fault as e: raise UserError(f'XML-RPC error: {e.faultString}') @@ -92,7 +101,7 @@ class FusionSyncConfig(models.Model): def action_test_connection(self): self.ensure_one() try: - uid, models_proxy = self._get_xmlrpc_connection() + uid, models_proxy = self._get_xmlrpc_connection(force=True) version_info = xmlrpc.client.ServerProxy( f'{self.url.rstrip("/")}/xmlrpc/2/common', allow_none=True ).version() @@ -174,6 +183,9 @@ class FusionSyncConfig(models.Model): 'state': 'error', 'last_sync': fields.Datetime.now(), 'last_sync_status': error_msg, + # Drop the cached uid so the next run re-authenticates fresh + # (the failure may have been a stale/invalid cached uid). + 'remote_uid': False, }) self.env['fusion.sync.log'].create({ 'config_id': self.id, @@ -544,7 +556,7 @@ class FusionSyncConfig(models.Model): def _create_remote_sale_order(self, product_mapping, qty, partner_name): """Create a sale order on the remote instance for inter-company transfers.""" self.ensure_one() - uid, models_proxy = self._get_xmlrpc_connection() + uid, models_proxy = self._get_xmlrpc_connection(force=True) partners = models_proxy.execute_kw( self.db_name, uid, self.api_key, @@ -593,7 +605,7 @@ class FusionSyncConfig(models.Model): def _create_remote_invoice(self, remote_so_id): """Create and post an invoice for a remote sale order.""" self.ensure_one() - uid, models_proxy = self._get_xmlrpc_connection() + uid, models_proxy = self._get_xmlrpc_connection(force=True) models_proxy.execute_kw( self.db_name, uid, self.api_key, diff --git a/fusion_login_audit/__manifest__.py b/fusion_login_audit/__manifest__.py index 01c2ae6d..9f74ff0d 100644 --- a/fusion_login_audit/__manifest__.py +++ b/fusion_login_audit/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Login Audit', - 'version': '19.0.1.0.0', + 'version': '19.0.1.1.0', 'category': 'Tools', 'summary': 'Durable login audit log with geo-enrichment, retention, and failure alerts.', 'description': """ diff --git a/fusion_login_audit/models/fusion_login_audit.py b/fusion_login_audit/models/fusion_login_audit.py index 3cc5ebc2..c2a67e5a 100644 --- a/fusion_login_audit/models/fusion_login_audit.py +++ b/fusion_login_audit/models/fusion_login_audit.py @@ -72,6 +72,17 @@ class FusionLoginAudit(models.Model): string='Device Type', default='unknown', ) database = fields.Char(string='Database', size=64) + login_kind = fields.Selection( + [ + ('interactive', 'Interactive'), + ('service', 'Service / Automation'), + ], + string='Login Kind', default='interactive', index=True, + help="Interactive = a real browser/HTTP login. Service = server-to-server " + "auth (XML-RPC/JSON-RPC, cron, inter-instance sync) with no HTTP " + "request. Service logins are hidden from the default Login Events " + "view to keep it focused on real user activity.", + ) # Odoo 19 replaces the legacy `_sql_constraints = [...]` list with # declarative `models.Constraint` attributes. The plan template used the diff --git a/fusion_login_audit/models/res_users.py b/fusion_login_audit/models/res_users.py index c776b244..818fffb2 100644 --- a/fusion_login_audit/models/res_users.py +++ b/fusion_login_audit/models/res_users.py @@ -86,11 +86,16 @@ class ResUsers(models.Model): else: vals['device_type'] = 'unknown' vals['geo_lookup_state'] = 'pending' + # A live HTTP request means a real browser/interactive login. + vals['login_kind'] = 'interactive' else: vals['ip_address'] = 'internal' vals['user_agent_raw'] = '' vals['device_type'] = 'unknown' vals['geo_lookup_state'] = 'internal' + # No HTTP request = server-to-server auth (XML-RPC/JSON-RPC, cron, + # inter-instance sync). Tagged so the default view can hide it. + vals['login_kind'] = 'service' # _credential is accepted in the signature so callers (T6 _check_credentials, # T7 _login) can hand the dict in without filtering. The helper deliberately @@ -388,5 +393,6 @@ class ResUsers(models.Model): 'view_mode': 'list,form', 'domain': [('user_id', '=', self.id)], 'context': {'create': False, 'edit': False, 'delete': False, - 'default_user_id': self.id}, + 'default_user_id': self.id, + 'search_default_filter_interactive': 1}, } diff --git a/fusion_login_audit/tests/test_login_audit.py b/fusion_login_audit/tests/test_login_audit.py index e6926e64..4cf74c8f 100644 --- a/fusion_login_audit/tests/test_login_audit.py +++ b/fusion_login_audit/tests/test_login_audit.py @@ -74,6 +74,7 @@ class TestFusionLoginAuditModel(TransactionCase): self.assertEqual(vals['ip_address'], 'internal') self.assertEqual(vals['user_agent_raw'], '') self.assertEqual(vals['geo_lookup_state'], 'internal') + self.assertEqual(vals['login_kind'], 'service') self.assertEqual(vals['database'], self.env.cr.dbname) def test_build_event_vals_parses_user_agent(self): @@ -91,6 +92,7 @@ class TestFusionLoginAuditModel(TransactionCase): self.assertIn('Windows', vals['os']) self.assertEqual(vals['device_type'], 'desktop') self.assertEqual(vals['geo_lookup_state'], 'pending') + self.assertEqual(vals['login_kind'], 'interactive') def test_build_event_vals_strips_password(self): """If a credential dict sneaks in, no password leaks into vals.""" diff --git a/fusion_login_audit/views/fusion_login_audit_views.xml b/fusion_login_audit/views/fusion_login_audit_views.xml index 526e5b1a..df81aec8 100644 --- a/fusion_login_audit/views/fusion_login_audit_views.xml +++ b/fusion_login_audit/views/fusion_login_audit_views.xml @@ -14,6 +14,7 @@ + @@ -36,6 +37,7 @@ + @@ -77,6 +79,11 @@ + + + + @@ -104,7 +113,10 @@ fusion.login.audit list,form - {} + + {'search_default_filter_interactive': 1} diff --git a/fusion_plating/fusion_plating_configurator/__manifest__.py b/fusion_plating/fusion_plating_configurator/__manifest__.py index eafac894..8b0bd91b 100644 --- a/fusion_plating/fusion_plating_configurator/__manifest__.py +++ b/fusion_plating/fusion_plating_configurator/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Configurator', - 'version': '19.0.23.6.0', + 'version': '19.0.22.10.0', 'category': 'Manufacturing/Plating', 'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.', 'description': """ @@ -44,7 +44,6 @@ Provides: 'views/fp_part_catalog_views.xml', 'views/fp_process_node_part_scoped_views.xml', 'views/fp_pricing_rule_views.xml', - 'views/fp_additional_charge_type_views.xml', 'views/fp_quote_configurator_views.xml', 'views/sale_order_views.xml', 'views/res_partner_views.xml', @@ -60,7 +59,6 @@ Provides: 'views/fp_configurator_menu.xml', 'views/fp_so_job_sort_views.xml', 'data/fp_sale_description_template_data.xml', - 'data/fp_additional_charge_type_data.xml', ], 'assets': { 'web.assets_backend': [ diff --git a/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml b/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml index f2f9a2bd..5d4e7578 100644 --- a/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml +++ b/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml @@ -233,10 +233,6 @@ OPEN open the part record -
- - Lot Order — price each line as a flat lot total (qty preserved for production) -