From 8c83703c30567285751cd99fa38c637a99b94428 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Tue, 27 Jan 2026 11:39:27 +0700 Subject: [PATCH] first commit --- __init__.py | 1 + __manifest__.py | 15 ++++++ __pycache__/__init__.cpython-312.pyc | Bin 0 -> 204 bytes models/__init__.py | 1 + models/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 228 bytes .../approval_cleaner_wizard.cpython-312.pyc | Bin 0 -> 2090 bytes models/approval_cleaner_wizard.py | 48 ++++++++++++++++++ security/ir.model.access.csv | 2 + tests/__init__.py | 1 + tests/test_approval_cleaner.py | 38 ++++++++++++++ views/approval_cleaner_wizard_views.xml | 34 +++++++++++++ 11 files changed, 140 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-312.pyc create mode 100644 models/__pycache__/approval_cleaner_wizard.cpython-312.pyc create mode 100644 models/approval_cleaner_wizard.py create mode 100644 security/ir.model.access.csv create mode 100644 tests/__init__.py create mode 100644 tests/test_approval_cleaner.py create mode 100644 views/approval_cleaner_wizard_views.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..c8d54a6 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,15 @@ +{ + 'name': 'Approval Cleaner', + 'version': '1.0', + 'summary': 'Delete approvals older than a specified date', + 'category': 'Administration', + 'author': 'Antigravity', + 'depends': ['approvals'], + 'data': [ + 'security/ir.model.access.csv', + 'views/approval_cleaner_wizard_views.xml', + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6e6131f7b345f844703e36b93ec01fd9f1aaca2 GIT binary patch literal 204 zcmX@j%ge<81h361GDU&(V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c zY`OUVNJ$c z;)w+XMfqikIq}Iksfl^1Me*gCRf$C@ews|T7>byIidHgw1{w7$KtCftH&ws5G$XYr zrBXkjvLquvFF8LYRX;x^Ki|+oKe@EHBtJJXB_%(vSRdV3{am2RoMQd>_{_Y_lK6PN kg34bUHbBABoK(9a4xj}f2Ni=DAD9^#8SgQu7O??20F*yI!TKGwY;w zU6Df%>8Wr^5S1t=gu;P8fny~uwi3x^R754Xa7z@plnXPqH?~6Hz#L}2{eACmzWMgY zNF)r{3eVlMe+mKcEB^?V(joTe8L3dSIk`Zb@xA7b+`h^>_Y}mZ)0rAaV#*7i{D}LIoo&(7uld!q+kOu+`NE;2@xD z4BY?-KaIVCW&pX=N?lM@Oy{i`ZK+ovRKqU4%cj zD<+1OgxP^JRvXW(b{K(ts-uwVXlU16n7U^A=uYMAHRK>4rA)_31$Icv!iaHU>Jzl! zVU+UoHc3H-@FEZ&+wLOOFj_9z7(puMkyEsUp6oS7^9t@a8M*}CWg zG8wT0^c)M)qVPaqJh=z#kWds;GiwQp(hF)ej?ol>5+LV^QNG}#h0<5cQU*!5b-M! z8)r=4H?4dDxjt*0K8%RbdO=jUgn6P=<&X`jXuFk>0}DYH`>0UFJb<)_W9-0#Y^w}# zyOwWzE@Ay{Guw0++N9{1WyAG+d%?C$&ZVK-CCfsDRHjanp4QHrD~R}tF{ZhRl{;Tg3=0AeA{;r4HcMia}nW0D;46~*4;9dU38CS z+Xf}yT%>Bz#Abn{!!(RsQo_jaTtqcqIRm+nO2}QIGATiiy)9&7D^Dc{xm2>*h*4It z%_^*xT*r3rVm?(^1TsO+0#%_`Fm0C(1(hR)?-}i^sA72~R+|3q6%fL;Q+&NNvU#t7 zvZRzp7?!iQ%6{H+EjHb<9`w9(Z)L3#@tJi9S(&qeeO7_i+{w1*;6HFyC_a!f#WIbz zuc)nI1O`ZD=E_kctaG7yF`W@-c3 zb+wtC{wn`PzIOUTJ^6McIag24Z6~iZk~izgo6q9g$y+sT{CRAsHhg+Jc4l4KRY78W zBma4R^V51f+lbHBF0M9X)e4Eq(uLE z|DFc)L{m>R^vSwDS(~}~Y`LENU`PM(g*M*Mrt8}Dj`qr)8VDupdU8ikF_$A}nnUT| zM#nZUd^z!ae4;ja`3Gft{6_QmiRRc;b9Ab9;&Scy++M$$IJOrG$HKcH+{c` zW*DwnK!!oX27g8+hts-YESF5D!%#Sfcfa8GN1N%ijnv%+r9-j!|55m z@oC-KxFk8nwuhjppIp1%9GY2s??rTMedY1}NB5t;`n_L!XL%=z*KY31r<8Ep1lOKV zYgFc)gZb2Nd2F>RjdC!9?pdf63&ASm(_?qA=@R~Q-{(Knb^zzO=0Q*|wTtZ>FXIc0 p!ACYpv29lp1mPDjT?f-YgOh*AGeUUtMq}b!ed64ofPJ(q{0+1Y9aI1S literal 0 HcmV?d00001 diff --git a/models/approval_cleaner_wizard.py b/models/approval_cleaner_wizard.py new file mode 100644 index 0000000..581eeee --- /dev/null +++ b/models/approval_cleaner_wizard.py @@ -0,0 +1,48 @@ +from odoo import models, fields, _ +from odoo.exceptions import UserError + +class ApprovalCleanerWizard(models.TransientModel): + _name = 'approval.cleaner.wizard' + _description = 'Approval Cleaner Wizard' + + date_end = fields.Date(string="End Date", required=True, help="Delete all approvals created before this date") + + def action_clean_approvals(self): + self.ensure_one() + # Search for approvals created before the specified date + # using 'create_date' as it is the standard Odoo field for creation time. + # However, the user request mentioned "Date below specified date i provide". + # 'approval.request' has a 'date' field (which seems to be a datetime) and 'create_date'. + # I will use 'date' field as per the user's initial description if implicit, but looking at approval_request.py + # date = fields.Datetime(string="Date") + # So I will use that. + + domain = [('date', '<', self.date_end)] + approvals_to_delete = self.env['approval.request'].sudo().search(domain) + count = len(approvals_to_delete) + + # Detach attachments to bypass ir.attachment unlink restriction + attachments = self.env['ir.attachment'].sudo().search([ + ('res_model', '=', 'approval.request'), + ('res_id', 'in', approvals_to_delete.ids), + ]) + if attachments: + attachments.write({'res_model': 'approval.cleaner.temp', 'res_id': 0}) + + approvals_to_delete.unlink() + + # Now delete the attachments safely + if attachments: + attachments.unlink() + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Success'), + 'message': _('%s approvals have been deleted.', count), + 'type': 'success', + 'sticky': False, + 'next': {'type': 'ir.actions.act_window_close'}, + } + } diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..9558725 --- /dev/null +++ b/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_approval_cleaner_wizard,approval.cleaner.wizard,model_approval_cleaner_wizard,approvals.group_approval_manager,1,1,1,1 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..234dbc7 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +from . import test_approval_cleaner diff --git a/tests/test_approval_cleaner.py b/tests/test_approval_cleaner.py new file mode 100644 index 0000000..3be94b3 --- /dev/null +++ b/tests/test_approval_cleaner.py @@ -0,0 +1,38 @@ +from odoo.tests import common +from odoo import fields +from datetime import timedelta + +class TestApprovalCleaner(common.TransactionCase): + def setUp(self): + super(TestApprovalCleaner, self).setUp() + self.ApprovalRequest = self.env['approval.request'] + self.category = self.env['approval.category'].create({ + 'name': 'Test Category', + 'has_date': 'required', + }) + + # Create approvals with different dates + self.approval_old = self.ApprovalRequest.create({ + 'name': 'Old Approval', + 'category_id': self.category.id, + 'date': fields.Datetime.now() - timedelta(days=100), + }) + + self.approval_new = self.ApprovalRequest.create({ + 'name': 'New Approval', + 'category_id': self.category.id, + 'date': fields.Datetime.now() - timedelta(days=10), + }) + + def test_clean_approvals(self): + wizard = self.env['approval.cleaner.wizard'].create({ + 'date_end': fields.Date.today() - timedelta(days=30), + }) + + wizard.action_clean_approvals() + + # Check that old approval is deleted + self.assertFalse(self.approval_old.exists(), "Old approval should be deleted") + + # Check that new approval still exists + self.assertTrue(self.approval_new.exists(), "New approval should not be deleted") diff --git a/views/approval_cleaner_wizard_views.xml b/views/approval_cleaner_wizard_views.xml new file mode 100644 index 0000000..f442e8d --- /dev/null +++ b/views/approval_cleaner_wizard_views.xml @@ -0,0 +1,34 @@ + + + + approval.cleaner.wizard.form + approval.cleaner.wizard + +
+ + + + +
+
+
+
+
+ + + Clean Approvals + approval.cleaner.wizard + form + new + + + +