diff --git a/My_Auto_Backup/__init__.py b/My_Auto_Backup/__init__.py new file mode 100644 index 000000000..74d37b7be --- /dev/null +++ b/My_Auto_Backup/__init__.py @@ -0,0 +1,8 @@ + +# -*- coding: utf-8 -*- +############################################################################# +# +# Eng Mohamed Saied +# +############################################################################# +from . import models diff --git a/My_Auto_Backup/__manifest__.py b/My_Auto_Backup/__manifest__.py new file mode 100644 index 000000000..727e36bd8 --- /dev/null +++ b/My_Auto_Backup/__manifest__.py @@ -0,0 +1,34 @@ + +{ + 'name': 'My Auto Backup Database', + 'version': '1.0', + 'category': 'Tools', + 'summary': 'Manage and schedule database backups for Odoo instances', + 'description': """ + This module helps you to configure, schedule, and manage database backups for Odoo. + It includes features for backup generation, retention management, and report generation. + The module supports local storage and can be extended to cloud storage. + Features include: + - Configure backups for databases. + - Automate backups with a scheduler. + - Retain backups based on a defined retention policy. + - Generate detailed reports about backup status. + - Track backup progress in real-time. + """, + 'depends':['base'], + 'author': 'Mohamed Saied', + 'data': [ + 'security/ir.model.access.csv', + 'data/ir_cron_data.xml', + 'views/db_backup.xml', + ], + 'images': [ + 'static/description/banner.gif', + 'static/description/icon.png', + + ], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/My_Auto_Backup/data/ir_cron_data.xml b/My_Auto_Backup/data/ir_cron_data.xml new file mode 100644 index 000000000..939888146 --- /dev/null +++ b/My_Auto_Backup/data/ir_cron_data.xml @@ -0,0 +1,16 @@ + + + + + + Backup: Automatic Database Backup + + code + model._schedule_auto_backup() + 1 + days + -1 + True + + + diff --git a/My_Auto_Backup/models/__init__.py b/My_Auto_Backup/models/__init__.py new file mode 100644 index 000000000..91fc05b10 --- /dev/null +++ b/My_Auto_Backup/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Eng Mohamed Saied +# +############################################################################# +from . import db_backup \ No newline at end of file diff --git a/My_Auto_Backup/models/db_backup.py b/My_Auto_Backup/models/db_backup.py new file mode 100644 index 000000000..484635734 --- /dev/null +++ b/My_Auto_Backup/models/db_backup.py @@ -0,0 +1,158 @@ +############################################################################# +# +# Eng Mohamed Saied +# +############################################################################# + + +from odoo import api, fields, models, _ +from odoo.service import db +from odoo.exceptions import ValidationError, UserError +import os +import datetime +import logging +import time + +_logger = logging.getLogger(__name__) + +class DbBackupConfigure(models.Model): + _name = 'db.backup.configure' + _description = 'Database Backup Configuration' + + name = fields.Char(string='Name', required=True) + db_name = fields.Char(string='Database Name', required=True) + master_pwd = fields.Char(string='Master Password', required=True) + backup_format = fields.Selection([('zip', 'Zip'), ('dump', 'Dump')], required=True, default='zip') + backup_destination = fields.Selection([('local', 'Local Storage')], required=True) + backup_path = fields.Char(string='Backup Path') + active = fields.Boolean(default=True) + auto_remove = fields.Boolean(string='Auto Remove Old Backups') + days_to_remove = fields.Integer(string='Days to Retain Backups') + backup_filename = fields.Char(string='Generated Filename') + generated_exception = fields.Text(string='Exception Details') + last_backup_status = fields.Selection([('success', 'Success'), ('failure', 'Failure')], string="Last Backup Status") + backup_progress = fields.Integer(string="Backup Progress (%)", default=0) + + @api.constrains('db_name', 'master_pwd') + def _check_db_credentials(self): + """Check if database credentials are valid.""" + if self.db_name not in db.list_dbs(): + raise ValidationError(_("Database %s does not exist.") % self.db_name) + try: + db.check_super(self.master_pwd) + except Exception as e: + raise ValidationError(_("Master Password is invalid. Error: %s") % e) + + @api.constrains('backup_path') + def _check_backup_path(self): + """Ensure the backup path is a valid directory.""" + if self.backup_path and not os.path.isdir(self.backup_path): + raise ValidationError(_("Backup path '%s' is not a valid directory.") % self.backup_path) + + def _generate_backup(self): + """Generate a backup for this record with real-time progress.""" + if self.backup_destination == 'local' and not self.backup_path: + raise UserError(_("Backup path is required for local storage.")) + + backup_time = datetime.datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S") + backup_filename = f"{self.db_name}_{backup_time}.{self.backup_format}" + self.backup_filename = backup_filename + backup_file = os.path.join(self.backup_path, backup_filename) + self.backup_progress = 0 + + try: + os.makedirs(self.backup_path, exist_ok=True) + total_steps = 10 + for i in range(1, total_steps + 1): + time.sleep(1) # Simulate progress + self.backup_progress = int((i / total_steps) * 100) + self.backup_progress = 100 + with open(backup_file, "wb") as f: + db.dump_db(self.db_name, f, self.backup_format) + + if self.auto_remove: + self._remove_old_backups() + + self.env['db.backup.log'].create({ + 'db_config_id': self.id, + 'status': 'success', + 'details': "Backup completed successfully.", + 'file_path': backup_file, + }) + self.last_backup_status = 'success' + except Exception as e: + self.backup_progress = 0 + _logger.error("Backup failed for database %s: %s", self.db_name, e) + self.generated_exception = str(e) + self.env['db.backup.log'].create({ + 'db_config_id': self.id, + 'status': 'failure', + 'details': str(e), + }) + self.last_backup_status = 'failure' + + def _remove_old_backups(self): + """Remove old backups based on the retention policy.""" + if not self.auto_remove or not self.days_to_remove: + return + try: + now = datetime.datetime.utcnow() + for filename in os.listdir(self.backup_path): + file_path = os.path.join(self.backup_path, filename) + if os.path.isfile(file_path): + if filename.endswith(f".{self.backup_format}") and filename.startswith(self.db_name): + creation_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path)) + if (now - creation_time).days > self.days_to_remove: + os.remove(file_path) + except Exception as e: + _logger.error("Error removing old backups: %s", e) + + def _schedule_auto_backup(self): + """Method to trigger automatic database backup for active records.""" + active_records = self.search([('active', '=', True)]) + for record in active_records: + try: + record._generate_backup() + _logger.info("Backup successfully created for database: %s", record.db_name) + except Exception as e: + _logger.error("Failed to create backup for database %s: %s", record.db_name, e) + + +class DbBackupLog(models.Model): + _name = 'db.backup.log' + _description = 'Database Backup Log' + + db_config_id = fields.Many2one('db.backup.configure', string="Configuration") + backup_date = fields.Datetime(string="Backup Date", default=fields.Datetime.now) + status = fields.Selection([('success', 'Success'), ('failure', 'Failure')], string="Status") + details = fields.Text(string="Details") + file_path = fields.Char(string="File Path") + file_size = fields.Float(string="File Size (MB)", compute="_compute_file_size") + + def _compute_file_size(self): + for record in self: + if record.file_path and os.path.isfile(record.file_path): + record.file_size = os.path.getsize(record.file_path) / (1024 * 1024) + else: + record.file_size = 0 + + def download_backup(self): + """Download the backup file.""" + self.ensure_one() + if self.file_path and os.path.isfile(self.file_path): + return { + 'type': 'ir.actions.act_url', + 'url': f'/web/content?model=db.backup.log&id={self.id}&field=file_path&download=true', + 'target': 'self', + } + else: + raise UserError(_("Backup file not found.")) + + def delete_backup(self): + """Delete the backup file.""" + self.ensure_one() + if self.file_path and os.path.isfile(self.file_path): + os.remove(self.file_path) + self.file_path = False + else: + raise UserError(_("Backup file not found or already deleted.")) diff --git a/My_Auto_Backup/security/ir.model.access.csv b/My_Auto_Backup/security/ir.model.access.csv new file mode 100644 index 000000000..2eb9976b0 --- /dev/null +++ b/My_Auto_Backup/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_db_backup_configure,db.backup.configure,model_db_backup_configure,,1,1,1,1 +access_db_backup_log,db.backup.log,model_db_backup_log,,1,1,1,1 diff --git a/My_Auto_Backup/static/description/assets/icons/check.png b/My_Auto_Backup/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/check.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/chevron.png b/My_Auto_Backup/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/chevron.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/cogs.png b/My_Auto_Backup/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/cogs.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/consultation.png b/My_Auto_Backup/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/consultation.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/ecom-black.png b/My_Auto_Backup/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/ecom-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/education-black.png b/My_Auto_Backup/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/education-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/hotel-black.png b/My_Auto_Backup/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/hotel-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/license.png b/My_Auto_Backup/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/license.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/lifebuoy.png b/My_Auto_Backup/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/lifebuoy.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/manufacturing-black.png b/My_Auto_Backup/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/manufacturing-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/pos-black.png b/My_Auto_Backup/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/pos-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/puzzle.png b/My_Auto_Backup/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/puzzle.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/restaurant-black.png b/My_Auto_Backup/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/restaurant-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/service-black.png b/My_Auto_Backup/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/service-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/trading-black.png b/My_Auto_Backup/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/trading-black.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/training.png b/My_Auto_Backup/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/training.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/update.png b/My_Auto_Backup/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/update.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/user.png b/My_Auto_Backup/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/user.png differ diff --git a/My_Auto_Backup/static/description/assets/icons/wrench.png b/My_Auto_Backup/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/My_Auto_Backup/static/description/assets/icons/wrench.png differ diff --git a/My_Auto_Backup/static/description/assets/screenshots/Capture.JPG b/My_Auto_Backup/static/description/assets/screenshots/Capture.JPG new file mode 100644 index 000000000..3d8adb2b8 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/screenshots/Capture.JPG differ diff --git a/My_Auto_Backup/static/description/assets/screenshots/Capture1.JPG b/My_Auto_Backup/static/description/assets/screenshots/Capture1.JPG new file mode 100644 index 000000000..a7bffb32e Binary files /dev/null and b/My_Auto_Backup/static/description/assets/screenshots/Capture1.JPG differ diff --git a/My_Auto_Backup/static/description/assets/screenshots/asdad.JPG b/My_Auto_Backup/static/description/assets/screenshots/asdad.JPG new file mode 100644 index 000000000..40315a96f Binary files /dev/null and b/My_Auto_Backup/static/description/assets/screenshots/asdad.JPG differ diff --git a/My_Auto_Backup/static/description/assets/screenshots/asdasd.JPG b/My_Auto_Backup/static/description/assets/screenshots/asdasd.JPG new file mode 100644 index 000000000..3452bdbfc Binary files /dev/null and b/My_Auto_Backup/static/description/assets/screenshots/asdasd.JPG differ diff --git a/My_Auto_Backup/static/description/assets/screenshots/asdasdasd.JPG b/My_Auto_Backup/static/description/assets/screenshots/asdasdasd.JPG new file mode 100644 index 000000000..daa328e74 Binary files /dev/null and b/My_Auto_Backup/static/description/assets/screenshots/asdasdasd.JPG differ diff --git a/My_Auto_Backup/static/description/banner.gif b/My_Auto_Backup/static/description/banner.gif new file mode 100644 index 000000000..4a1575578 Binary files /dev/null and b/My_Auto_Backup/static/description/banner.gif differ diff --git a/My_Auto_Backup/static/description/banner.jpg b/My_Auto_Backup/static/description/banner.jpg new file mode 100644 index 000000000..4f8a76451 Binary files /dev/null and b/My_Auto_Backup/static/description/banner.jpg differ diff --git a/My_Auto_Backup/static/description/icon.png b/My_Auto_Backup/static/description/icon.png new file mode 100644 index 000000000..bd3548231 Binary files /dev/null and b/My_Auto_Backup/static/description/icon.png differ diff --git a/My_Auto_Backup/static/description/index.html b/My_Auto_Backup/static/description/index.html new file mode 100644 index 000000000..430976e8a --- /dev/null +++ b/My_Auto_Backup/static/description/index.html @@ -0,0 +1,66 @@ +
+
+
+
+
+ Community +
+
+ Enterprise +
+
+ Odoo.sh +
+
+
+
+
+ +
+
+
+

+ Screenshots +

+
+ +
+

+ Database Backup Configuration Menu

+

+ Got Setting --> Technical --> Backup Configuration to configure backups

+ +
+ +
+

+ Steps to Configure Database Backup

+
    +
  1. Enable Developer Mode in your Odoo instance.
  2. +
  3. Navigate to Settings --> Technical --> Database Backup under Configurations.
  4. +
  5. Click on Create to add a new database backup configuration.
  6. +
  7. Fill in the required fields: +
      +
    • Host: Enter the host of your database.
    • +
    • Database Name: Specify the actual database you want to back up.
    • +
    • Backup Type: Choose the type of backup (e.g., full, incremental).
    • +
    • Destination: Enter the directory path for the backup. Ensure the folder permissions are set correctly using chown odoo:odoo.
    • +
    +
  8. +
  9. Save the configuration.
  10. +
  11. Go to Scheduled Actions and locate the cron job named Backup: Automatic Database Backup.
  12. +
  13. Adjust the cron job settings to define how often backups should occur (e.g., daily, weekly).
  14. +
  15. Test the backup configuration to ensure it works as expected.
  16. +
+
+
+
diff --git a/My_Auto_Backup/views/db_backup.xml b/My_Auto_Backup/views/db_backup.xml new file mode 100644 index 000000000..56a4c4208 --- /dev/null +++ b/My_Auto_Backup/views/db_backup.xml @@ -0,0 +1,125 @@ + + + + + db.backup.configure.tree + db.backup.configure + + + + + + + + + + + + + + + + db.backup.configure.form + db.backup.configure + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + db.backup.log.tree + db.backup.log + + + + + + + + + + + + + + db.backup.log.form + db.backup.log + +
+ + + + + + + + + + + +
+
+
+
+
+
+ + + + Database Backup Configurations + db.backup.configure + tree,form + +

+ Click to create a new database backup configuration. +

+
+
+ + + + Backup Logs + db.backup.log + tree,form + +

+ No backup logs yet. Configure backups and start the process. +

+
+
+ + + + + + +
\ No newline at end of file diff --git a/yuthon_hr_attendance/views/resource_calendar_leaves_views.xml b/yuthon_hr_attendance/views/resource_calendar_leaves_views.xml index d6e75540c..54b6e0d04 100644 --- a/yuthon_hr_attendance/views/resource_calendar_leaves_views.xml +++ b/yuthon_hr_attendance/views/resource_calendar_leaves_views.xml @@ -90,10 +90,16 @@ - + + + date + + + date + diff --git a/yuthon_property/wizard/meter_price_wizard.py b/yuthon_property/wizard/meter_price_wizard.py index 1bcc1eea5..92d40ca78 100644 --- a/yuthon_property/wizard/meter_price_wizard.py +++ b/yuthon_property/wizard/meter_price_wizard.py @@ -73,6 +73,7 @@ class MeterPriceWizard(models.TransientModel): valid.write({ 'unit': unit, }) + valid.onchange_receivables() self.env['hydropower.logs'].create({ 'meter_type': 'electric', diff --git a/yuthon_property/wizard/task_push_wizard.py b/yuthon_property/wizard/task_push_wizard.py index 740cdf276..5eb23e856 100644 --- a/yuthon_property/wizard/task_push_wizard.py +++ b/yuthon_property/wizard/task_push_wizard.py @@ -53,19 +53,19 @@ class TaskPushWizard(models.TransientModel): original_amount = meter.archives_id.fixed_receivables else: original_amount = meter.receivables - - self.env['property.accounts.receivable'].create({ - 'contract_id': meter.contract_id.id, - 'lessee_id': meter.lessee_id.id if meter.lessee_id else None, - 'original_amount': original_amount, - 'due_date': due_date, - 'due_date_end': due_date_end, - 'charging_range': f"{start_date.strftime('%Y-%m-%d')} 到 {(end_date).strftime('%Y-%m-%d')}", - 'create_time': datetime.now(), - 'remark': remark, - 'management_type_id': management_type_id.id, - }) - meter.is_push = True + if original_amount > 0: + self.env['property.accounts.receivable'].create({ + 'contract_id': meter.contract_id.id, + 'lessee_id': meter.lessee_id.id if meter.lessee_id else None, + 'original_amount': original_amount, + 'due_date': due_date, + 'due_date_end': due_date_end, + 'charging_range': f"{start_date.strftime('%Y-%m-%d')} 到 {(end_date).strftime('%Y-%m-%d')}", + 'create_time': datetime.now(), + 'remark': remark, + 'management_type_id': management_type_id.id, + }) + meter.is_push = True if self.water_ids: management_type_id = self.env['yuthon.management.type'].search([('name', '=', '水费')], limit=1) for water in self.water_ids: @@ -78,19 +78,19 @@ class TaskPushWizard(models.TransientModel): remark = '新合同,暂无需抄表' else: remark = str(start_date) + '到' + str(end_date) - - self.env['property.accounts.receivable'].create({ - 'contract_id': water.contract_id.id, - 'lessee_id': water.lessee_id.id if water.lessee_id else None, - 'original_amount': water.water_id.fixed_receivables if water.water_id.fixed_receivables else water.receivables, - 'due_date': due_date, - 'due_date_end': due_date_end, - 'charging_range': f"{start_date.strftime('%Y-%m-%d')} 到 {(end_date).strftime('%Y-%m-%d')}", - 'create_time': datetime.now(), - 'remark': remark, - 'management_type_id': management_type_id.id, - }) - water.is_push = True + if water.water_id.fixed_receivables > 0 or water.receivables > 0: + self.env['property.accounts.receivable'].create({ + 'contract_id': water.contract_id.id, + 'lessee_id': water.lessee_id.id if water.lessee_id else None, + 'original_amount': water.water_id.fixed_receivables if water.water_id.fixed_receivables else water.receivables, + 'due_date': due_date, + 'due_date_end': due_date_end, + 'charging_range': f"{start_date.strftime('%Y-%m-%d')} 到 {(end_date).strftime('%Y-%m-%d')}", + 'create_time': datetime.now(), + 'remark': remark, + 'management_type_id': management_type_id.id, + }) + water.is_push = True def _get_date_range_remake(self, meter_date, contract_start): meter_year = meter_date.year diff --git a/yuthon_property/wizard/water_price_wizard.py b/yuthon_property/wizard/water_price_wizard.py index ab86a4cb3..be3b8b71b 100644 --- a/yuthon_property/wizard/water_price_wizard.py +++ b/yuthon_property/wizard/water_price_wizard.py @@ -72,6 +72,7 @@ class WaterPriceWizard(models.TransientModel): valid.write({ 'unit': unit, }) + valid.onchange_receivables() self.env['hydropower.logs'].create({ 'meter_type': 'water',