diff --git a/.github/workflows/block_reserved_branches.yml b/.github/workflows/block_reserved_branches.yml index 308ba4805..22e29c061 100644 --- a/.github/workflows/block_reserved_branches.yml +++ b/.github/workflows/block_reserved_branches.yml @@ -1,4 +1,3 @@ -# Author: Ahmad Samir name: Block Reserved Branches on: @@ -9,13 +8,54 @@ on: jobs: block-reserved-branches: runs-on: app-sever-project-runner + steps: - - name: Check for reserved or pattern-matching branch names + - name: Validate branch creator + reserved names env: GH_TOKEN: ${{ secrets.GH_TOKEN }} REPO: ${{ github.repository }} BRANCH_NAME: ${{ github.ref_name }} + CREATOR: ${{ github.actor }} run: | + echo "Branch: $BRANCH_NAME" + echo "Creator: $CREATOR" + + ####################################################### + # 🟦 1) Allowed Users List + ####################################################### + ALLOWED_USERS=( + "expsa" + "moutazmuhammad" + "ronozoro" + "Abubaker-Altaib" + "altexp" + "the5abir" + "ahmadaking" + "kchyounes19" + "abdurrahman-saber" + ) + + IS_ALLOWED="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "$CREATOR" == "$user" ]]; then + IS_ALLOWED="true" + break + fi + done + + if [[ "$IS_ALLOWED" == "false" ]]; then + echo "❌ User '$CREATOR' is NOT allowed to create branches. Deleting..." + curl -s -X DELETE \ + -H "Authorization: token $GH_TOKEN" \ + https://api.github.com/repos/$REPO/git/refs/heads/$BRANCH_NAME + exit 1 + fi + + echo "✔ User '$CREATOR' is allowed." + + ####################################################### + # 🟦 2) Reserved Branch Names (Your Existing List) + ####################################################### RESERVED_NAMES=( master dev_odex25_accounting @@ -82,18 +122,27 @@ jobs: preprod_odex-event preprod_openeducat_erp-14.0.1.0 ) - # Check if branch is an exact reserved name + + # Exact match for reserved in "${RESERVED_NAMES[@]}"; do if [[ "$BRANCH_NAME" == "$reserved" ]]; then echo "❌ Branch name '$BRANCH_NAME' is reserved. Deleting..." - curl -s -X DELETE -H "Authorization: token $GH_TOKEN" https://api.github.com/repos/$REPO/git/refs/heads/$BRANCH_NAME + curl -s -X DELETE \ + -H "Authorization: token $GH_TOKEN" \ + https://api.github.com/repos/$REPO/git/refs/heads/$BRANCH_NAME exit 1 fi done - # Check if branch name matches restricted patterns + + ####################################################### + # 🟦 3) Pattern-based Restriction + ####################################################### if [[ "$BRANCH_NAME" == master_* || "$BRANCH_NAME" == preprod_* || "$BRANCH_NAME" == dev_* ]]; then echo "❌ Branch name '$BRANCH_NAME' matches restricted pattern. Deleting..." - curl -s -X DELETE -H "Authorization: token $GH_TOKEN" https://api.github.com/repos/$REPO/git/refs/heads/$BRANCH_NAME + curl -s -X DELETE \ + -H "Authorization: token $GH_TOKEN" \ + https://api.github.com/repos/$REPO/git/refs/heads/$BRANCH_NAME exit 1 fi - echo "✅ Branch '$BRANCH_NAME' is allowed." \ No newline at end of file + + echo "✅ Branch '$BRANCH_NAME' is allowed." diff --git a/.github/workflows/restrict-pr-authors.yaml b/.github/workflows/restrict-pr-authors.yaml new file mode 100644 index 000000000..fd225bf7b --- /dev/null +++ b/.github/workflows/restrict-pr-authors.yaml @@ -0,0 +1,98 @@ +name: Restrict PR Authors & Committers + +permissions: + contents: read + pull-requests: write + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: + - dev_odex-event + - dev_odex25_accounting + - dev_odex25_base + - dev_odex25_dms + - dev_odex25_fleet + - dev_odex25_hr + - dev_odex25_inventory + - dev_odex25_maintenance + - dev_odex25_mobile + - dev_odex25_pos + - dev_odex25_project + - dev_odex25_purchase + - dev_odex25_realstate + - dev_odex25_sales + - dev_odex25_survey + - dev_odex25_transactions + - dev_odex25_website + - dev_openeducat_erp-14.0.1.0 + - dev_odex25_ensan + - dev_odex25_helpdesk + - dev_odex25_donation + +jobs: + check_pr_author: + runs-on: linting_odex25-standard-modules_runner + + steps: + - name: Validate PR Author & Commit Authors + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const allowed = [ + "expsa", + "moutazmuhammad", + "ronozoro", + "Abubaker-Altaib", + "altexp", + "the5abir", + "ahmadaking", + "kchyounes19", + "abdurrahman-saber" + ]; + + const pr = context.payload.pull_request; + const prAuthor = pr.user.login; + const owner = context.repo.owner; + const repo = context.repo.repo; + + core.info(`PR author: ${prAuthor}`); + + // Check PR author + if (!allowed.includes(prAuthor)) { + core.error(`Unauthorized PR author: ${prAuthor}. Closing PR...`); + await github.rest.pulls.update({ + owner, + repo, + pull_number: pr.number, + state: "closed" + }); + return; + } + + // Check commit authors + const commitList = await github.rest.pulls.listCommits({ + owner, + repo, + pull_number: pr.number + }); + + for (const commit of commitList.data) { + const commitAuthor = commit.author ? commit.author.login : null; + + if (commitAuthor && !allowed.includes(commitAuthor)) { + core.error(`Unauthorized commit author: ${commitAuthor}. Closing PR...`); + + await github.rest.pulls.update({ + owner, + repo, + pull_number: pr.number, + state: "closed" + }); + + return; + } + } + + core.info("All PR authors and committers are allowed."); diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 9a5223bbe..5eec3d59b 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -27,7 +27,7 @@ on: jobs: - sonar: + sonarqube_check: runs-on: sonarqube steps: - name: Checkout code diff --git a/odex25_ensan/odex_takaful/i18n/ar_001.po b/odex25_ensan/odex_takaful/i18n/ar_001.po index 926fbccd8..3582cee76 100644 --- a/odex25_ensan/odex_takaful/i18n/ar_001.po +++ b/odex25_ensan/odex_takaful/i18n/ar_001.po @@ -243,6 +243,11 @@ msgstr "تاريخ الدفع:" msgid "Payment Method:" msgstr "طريقة الدفع:" +#. module: enasan_geidea_sponsorship +#: model_terms:ir.ui.view,arch_db:enasan_geidea_sponsorship.view_account_payment_register_form_geidea +msgid "Process on Terminal" +msgstr "الدفع على المنصة" + #. module: odex_takaful #: model_terms:ir.ui.view,arch_db:odex_takaful.report_transfer_deduction_document msgid "Payment Method" @@ -2234,7 +2239,7 @@ msgstr "تمديد" #: model_terms:ir.ui.view,arch_db:odex_takaful.donations_details_lines_view_form #, python-format msgid "Extend Donation" -msgstr "تمديد التبرع" +msgstr "تمديد الكفالة" #. module: odex_takaful #: model:ir.model.fields.selection,name:odex_takaful.selection__donations_details_lines__state__extended @@ -7504,4 +7509,57 @@ msgstr "سجل الإستبدال" msgid "Paid" msgstr "منتهي" +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__check_number +msgid "Check Number" +msgstr "رقم الشيك" +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__journal_id +msgid "Journal" +msgstr "حساب الجمعية" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__partner_bank_id +msgid "Partner Bank" +msgstr "بنك المتبرع" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__payment_amount +msgid "Payment Amount" +msgstr "المبلغ" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__payment_file_attachment +msgid "Payment Attachment" +msgstr "مرفق الدفع" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__payment_method +msgid "Payment Method" +msgstr "طريقة الدفع" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__check_due_date +msgid "Check Due Date" +msgstr "تاريخ الإستحقاق" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_extension_payment_wizard_line__check_number +msgid "Check Number" +msgstr "رقم الشيك" + +#. module: odex_takaful +#: model:ir.model.fields,field_description:odex_takaful.field_donation_extension_wizard__is_different_payment +msgid "Is Different Payment" +msgstr "طرف دفع متعددة" + +#. module: odex_takaful +#: model_terms:ir.ui.view,arch_db:odex_takaful.view_account_payment_register_form +msgid "Association Journal" +msgstr "حساب الجمعية" + +#. module: odex_takaful +#: model_terms:ir.ui.view,arch_db:odex_takaful.donation_extension_wizard_form +msgid "Sub Payments" +msgstr "دفع متعدد" \ No newline at end of file diff --git a/odex25_ensan/odex_takaful/models/donation_details_lines.py b/odex25_ensan/odex_takaful/models/donation_details_lines.py index e600b2a65..ee84a3f7b 100644 --- a/odex25_ensan/odex_takaful/models/donation_details_lines.py +++ b/odex25_ensan/odex_takaful/models/donation_details_lines.py @@ -99,6 +99,83 @@ class DonationsDetailsLines(models.Model): journal_id = fields.Many2one('account.journal', string="Journal",domain="[('type','=','bank')]",default=_default_journal_id) benefit_status = fields.Selection(related='benefit_id.member_status') ages = fields.Integer(compute='_compute_get_age_range' , store=True) + waiting_date = fields.Date( + string="تاريخ الانتظار", + ) + actual_end_date = fields.Date(string="مدة الانتظار", compute='_compute_actual_end_date', store=True) + period_display = fields.Char( + string="Period", + compute="_compute_period_display" ) + + age_category = fields.Selection([ + ('18', '+18'), + ('16', '+16'), + ('all', 'جميع الأعمار'), + ], string="الفئة العمرية", compute="_compute_age_category") + + @api.depends('sponsorship_duration', 'payment_month_count', 'direct_debit') + def _compute_age_category(self): + for rec in self: + print( rec.sponsorship_duration , ' ' ,rec.direct_debit , ' ' , rec.payment_month_count) + if rec.sponsorship_duration == 'permanent': + rec.age_category = 'all' + elif rec.sponsorship_duration != 'permanent' : + if rec.direct_debit: + rec.age_category = '16' + elif rec.sponsorship_duration != 'permanent' and rec.payment_month_count >= 6: + rec.age_category = 'all' + elif rec.sponsorship_duration != 'permanent' and rec.payment_month_count < 6: + rec.age_category = '18' + else: + rec.age_category = 'all' + + @api.depends('benefit_ids.end_date') + def _compute_actual_end_date(self): + for rec in self: + if rec.benefit_ids: + # take the first benefit record + first_benefit = rec.benefit_ids[0] + rec.actual_end_date = first_benefit.end_date + else: + rec.actual_end_date = False + @api.depends('end_date', 'waiting_date') + def _compute_period_display(self): + today = date.today() + for rec in self: + + # pick whichever date is set + if rec.state == 'replace' : + base_date = rec.end_date + elif rec.state == 'waiting': + base_date = rec.waiting_date + else: + base_date = False + if base_date: + delta = relativedelta(today, base_date) + # build human-readable string + if delta.years > 0: + rec.period_display = f"{delta.years} year(s)" + elif delta.months > 0: + rec.period_display = f"{delta.months} month(s)" + elif delta.days > 0: + rec.period_display = f"{delta.days} day(s)" + else: + rec.period_display = "Today" + else: + rec.period_display = "No date" + + + + @api.constrains('state') + def _compute_dates(self): + """Automatically set waiting_date or replace_date based on state.""" + for rec in self: + if rec.state == 'waiting': + rec.waiting_date = date.today() + else: + rec.waiting_date = False + + # cheque_number = fields.Integer(string="Cheque Number") # cheque_due_date = fields.Date(string="Cheque Due Date") # cheque_file_attachment = fields.Binary(string='Cheque Attachment', attachment=True) @@ -157,7 +234,7 @@ class DonationsDetailsLines(models.Model): today = fields.Date.today() for rec in self: show_extend_button = ( - ((rec.record_type == 'donation' and rec.direct_debit) or rec.record_type == 'sponsorship') and + rec.record_type == 'sponsorship' and rec.end_date and rec.end_date >= today and rec.state == 'active' @@ -168,9 +245,9 @@ class DonationsDetailsLines(models.Model): def _compute_show_replaced_button(self): for rec in self: show_replaced_button = ( - ((rec.record_type == 'donation' and rec.donation_mechanism == 'with_conditions') or rec.record_type == 'sponsorship') and - rec.state in ('active', 'paid','replace') and - (rec.benefit_id or rec.benefit_ids or rec.family_id) + rec.record_type == 'sponsorship' and + rec.state in ('active','replace') and + (rec.benefit_id or rec.benefit_ids) ) rec.show_replaced_button = show_replaced_button @@ -243,6 +320,12 @@ class DonationsDetailsLines(models.Model): for rec in self: rec.total_months_amount = rec.donation_amount * rec.payment_month_count + @api.onchange('sponsorship_duration') + def _onchange_sponsorship_duration(self): + for rec in self: + if rec.sponsorship_duration == "permanent": + rec.direct_debit = False + @api.onchange('product_template_id') def _onchange_product_template_id(self): for rec in self: @@ -507,6 +590,7 @@ class DonationsDetailsLines(models.Model): self._onchange_sponsorship_type() return res + @api.onchange('donation_types', 'donation_type') def _onchange_sponsorship_type(self): for rec in self: @@ -660,6 +744,16 @@ class DonationsDetailsLines(models.Model): domain = expression.AND([domain, [ ('age', '>=', benefit_age_limit) ]]) + if rec.sponsorship_id or rec.sponsorship_mechanism_id: + all_benefit_ids = ( + + ( + rec.sponsorship_mechanism_id.donations_details_lines_mechanism_ids if rec.sponsorship_mechanism_id else + self.env['donations.details.lines']) + ) + selected_benefit_ids = all_benefit_ids.mapped('benefit_ids').ids + domain = expression.AND([domain, [('id', 'not in', selected_benefit_ids)]]) + elif rec.record_type == 'donation' and rec.donation_mechanism == "with_conditions" and rec.family_id: domain = [("benefit_id", "=", rec.family_id.id)] members = self.env['family.member'].sudo().search(domain) @@ -816,7 +910,7 @@ class DonationsDetailsLines(models.Model): record = super(DonationsDetailsLines, self).create(vals) if record.benefit_ids == 1: record.benefit_id = record.benefit_ids[0].id - + print('Vals >>> ' , vals) return record def write(self, vals): @@ -864,6 +958,20 @@ class DonationsDetailsLines(models.Model): if sponsorship and len(sponsorship) == 1: message = _("Benefit IDs changed in a donation item:
%s") % "
".join(changes) sponsorship.message_post(body=message) + + # if 'state' in vals: + # print('In Vals') + # print("vals['state'] >>> " , vals['state']) + # if vals['state'] == 'waiting': + # vals['waiting_date'] = date.today() + # print("vals['waiting_date'] >>> " , vals['waiting_date']) + # vals['replace_date'] = False + # elif vals['state'] == 'replace': + # vals['replace_date'] = date.today() + # vals['waiting_date'] = False + # else: + # vals['waiting_date'] = False + # vals['replace_date'] = False self.onset_benefit_id() return res diff --git a/odex25_ensan/odex_takaful/models/res_partner.py b/odex25_ensan/odex_takaful/models/res_partner.py index dddf7dc1a..4167b994d 100644 --- a/odex25_ensan/odex_takaful/models/res_partner.py +++ b/odex25_ensan/odex_takaful/models/res_partner.py @@ -281,6 +281,14 @@ class ResPartner(models.Model): if 'mobile' in vals: self._check_phone_numbers(vals['mobile']) vals['mobile'] = self.phone_format(vals['mobile']) + if 'name' in vals : + if self.kafel_id: + self.kafel_id.name = vals['name'] + + if 'mobile' in vals : + if self.kafel_id: + self.kafel_id.login = vals['mobile'] + res = super(ResPartner, self).write(vals) return res diff --git a/odex25_ensan/odex_takaful/models/sponsorship_scheduling_line.py b/odex25_ensan/odex_takaful/models/sponsorship_scheduling_line.py index a5c49aa0b..f6b2316b5 100644 --- a/odex25_ensan/odex_takaful/models/sponsorship_scheduling_line.py +++ b/odex25_ensan/odex_takaful/models/sponsorship_scheduling_line.py @@ -148,7 +148,9 @@ class SchedulingLine(models.Model): line.sudo().write({'status': 'paid'}) if line.donation_detail_linked_id.state == 'waiting' or (not line.donation_detail_linked_id.benefit_id and line.donation_detail_linked_id.record_type == 'sponsorship'): line.donation_detail_linked_id.sudo().write({'state': 'waiting'}) - else: + elif line.donation_detail_linked_id.record_type == 'donation': + line.donation_detail_linked_id.sudo().write({'state': 'paid'}) + elif line.donation_detail_linked_id.state != 'waiting' and line.donation_detail_linked_id.record_type != 'donation' and line.donation_detail_linked_id.record_type == 'sponsorship': line.donation_detail_linked_id.sudo().write({'state': 'active'}) line.donation_detail_linked_id.sponsorship_id.sudo().write({'state': 'wait_pay'}) diff --git a/odex25_ensan/odex_takaful/models/takaful_sponorship_model.py b/odex25_ensan/odex_takaful/models/takaful_sponorship_model.py index 381b6f98e..18f6e8898 100644 --- a/odex25_ensan/odex_takaful/models/takaful_sponorship_model.py +++ b/odex25_ensan/odex_takaful/models/takaful_sponorship_model.py @@ -810,7 +810,7 @@ class TakafulSponsorship(models.Model): sponsor_donor_type = vals.get('sponsor_donor_type') sponsor_id = vals.get('sponsor_id') state = vals.get('state', 'draft') - + print('Vals >>> ' , vals) # Only validate when state is draft and saving (not during onchange) if state == 'draft': # Check for donation with new_sponsor diff --git a/odex25_ensan/odex_takaful/security/ir.model.access.csv b/odex25_ensan/odex_takaful/security/ir.model.access.csv index 9f7dfee61..2c2d827b1 100644 --- a/odex25_ensan/odex_takaful/security/ir.model.access.csv +++ b/odex25_ensan/odex_takaful/security/ir.model.access.csv @@ -49,7 +49,7 @@ access_donation_extension_wizard_line,donation.extension.wizard.line.access,mode access_donation_replacement_log,donation.replacement.log.access,model_donation_replacement_log,odex_takaful.group_kufula_user,1,1,1,0 access_replace_sponsor_wizard,replace.sponsor.wizard.access,model_replace_sponsor_wizard,odex_takaful.group_replace_sponsor,1,1,1,1 access_add_benefit_wizard,add.benefit.wizard.access,model_add_benefit_wizard,odex_takaful.group_kufula_user,1,1,1,1 - +access_extension_payment_wizard_line,extension.payment.wizard.line.access,model_extension_payment_wizard_line,odex_takaful.group_kufula_user,1,1,1,1 access_group_kufula_user_product_template,access_group_kufula_user_product_template,product.model_product_template,odex_takaful.group_kufula_user,1,1,0,0 access_group_kufula_user_account_move,access_group_kufula_user_account_move,account.model_account_move,odex_takaful.group_kufula_user,1,1,1,0 access_group_kufula_user_sale_order,access_group_kufula_user_sale_order,sale.model_sale_order,odex_takaful.group_kufula_user,1,1,1,0 diff --git a/odex25_ensan/odex_takaful/views/donations_details_lines.xml b/odex25_ensan/odex_takaful/views/donations_details_lines.xml index 5582f6347..ff32a550c 100644 --- a/odex25_ensan/odex_takaful/views/donations_details_lines.xml +++ b/odex25_ensan/odex_takaful/views/donations_details_lines.xml @@ -61,11 +61,12 @@ donations.details.lines.view.tree donations.details.lines - + + @@ -82,15 +83,16 @@ - + + - +