From d4e4cd67d5c07429f909aa964b6947b63924e400 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 16:29:15 +0300 Subject: [PATCH 01/11] Add or update delete-merged-branches workflow --- .github/workflows/delete-merged-branches.yml | 41 ++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/delete-merged-branches.yml diff --git a/.github/workflows/delete-merged-branches.yml b/.github/workflows/delete-merged-branches.yml new file mode 100644 index 000000000..ff902a5c7 --- /dev/null +++ b/.github/workflows/delete-merged-branches.yml @@ -0,0 +1,41 @@ +# Author: Ahmad Samir + name: Auto Delete Branch After Merge + + on: + pull_request: + types: [closed] # triggers when PR is closed (merged or just closed) + + jobs: + delete-merged-branch: + if: github.event.pull_request.merged == true + name: Delete Merged Branch + runs-on: app-sever-project-runner # your self-hosted runner + + steps: + - name: Delete merged branch (with rules) + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + REPO: ${{ github.repository }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + run: | + echo "๐Ÿ” Base branch: $BASE_REF" + echo "๐Ÿ” Head branch: $HEAD_REF" + + # โŒ Do not delete dev_* merged into preprod_* + if [[ "$HEAD_REF" == dev_* && "$BASE_REF" == preprod_* ]]; then + echo "๐Ÿšซ Rule: Do not delete dev_* merged into preprod_*" + exit 0 + fi + + # โŒ Do not delete preprod_* merged into master_* + if [[ "$HEAD_REF" == preprod_* && "$BASE_REF" == master_* ]]; then + echo "๐Ÿšซ Rule: Do not delete preprod_* merged into master_*" + exit 0 + fi + + echo "โœ… Allowed to delete '$HEAD_REF' from '$REPO'" + + curl -s -X DELETE -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$REPO/git/refs/heads/$HEAD_REF -w " +๐Ÿงน Deleted branch '$HEAD_REF' from '$REPO' โ€” HTTP %{http_code} +" \ No newline at end of file From c0bd59878c13d1851783d8251a3edb917bb9959c Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 17:12:36 +0300 Subject: [PATCH 02/11] Add or update prevent-invalid-branch-merges workflow --- .../prevent-invalid-branch-merges.yml | 294 +++++------------- 1 file changed, 73 insertions(+), 221 deletions(-) diff --git a/.github/workflows/prevent-invalid-branch-merges.yml b/.github/workflows/prevent-invalid-branch-merges.yml index 24bb05fbb..b9303a11e 100644 --- a/.github/workflows/prevent-invalid-branch-merges.yml +++ b/.github/workflows/prevent-invalid-branch-merges.yml @@ -25,7 +25,6 @@ on: - dev_odex25_ensan - dev_odex25_helpdesk - dev_odex25_donation - - preprod_odex-event - preprod_odex25_accounting - preprod_odex25_base @@ -47,8 +46,7 @@ on: - preprod_odex25_ensan - preprod_odex25_helpdesk - preprod_odex25_donation - - - master_odex-event + - master_odex-event - master_odex25_accounting - master_odex25_base - master_odex25_dms @@ -66,239 +64,93 @@ on: - master_odex25_transactions - master_odex25_website - master_openeducat_erp-14.0.1.0 - - master_odex25_ensan + - master_odex25_ensan - master_odex25_helpdesk - master_odex25_donation - - + +concurrency: + group: prevent-invalid-merges-${{ github.head_ref }} + cancel-in-progress: true jobs: validate-merge-source: runs-on: app-sever-project-runner + strategy: + matrix: + base_head: + - { base: preprod_odex-event, head: dev_odex-event } + - { base: preprod_odex25_accounting, head: dev_odex25_accounting } + - { base: preprod_odex25_base, head: dev_odex25_base } + - { base: preprod_odex25_dms, head: dev_odex25_dms } + - { base: preprod_odex25_fleet, head: dev_odex25_fleet } + - { base: preprod_odex25_hr, head: dev_odex25_hr } + - { base: preprod_odex25_inventory, head: dev_odex25_inventory } + - { base: preprod_odex25_maintenance, head: dev_odex25_maintenance } + - { base: preprod_odex25_mobile, head: dev_odex25_mobile } + - { base: preprod_odex25_pos, head: dev_odex25_pos } + - { base: preprod_odex25_project, head: dev_odex25_project } + - { base: preprod_odex25_purchase, head: dev_odex25_purchase } + - { base: preprod_odex25_realstate, head: dev_odex25_realstate } + - { base: preprod_odex25_sales, head: dev_odex25_sales } + - { base: preprod_odex25_survey, head: dev_odex25_survey } + - { base: preprod_odex25_transactions, head: dev_odex25_transactions } + - { base: preprod_odex25_website, head: dev_odex25_website } + - { base: preprod_openeducat_erp-14.0.1.0, head: dev_openeducat_erp-14.0.1.0 } + - { base: preprod_odex25_ensan, head: dev_odex25_ensan } + - { base: preprod_odex25_helpdesk, head: dev_odex25_helpdesk } + - { base: preprod_odex25_donation, head: dev_odex25_donation } + - { base: master_odex-event, head: preprod_odex-event } + - { base: master_odex25_accounting, head: preprod_odex25_accounting } + - { base: master_odex25_base, head: preprod_odex25_base } + - { base: master_odex25_dms, head: preprod_odex25_dms } + - { base: master_odex25_fleet, head: preprod_odex25_fleet } + - { base: master_odex25_hr, head: preprod_odex25_hr } + - { base: master_odex25_inventory, head: preprod_odex25_inventory } + - { base: master_odex25_maintenance, head: preprod_odex25_maintenance } + - { base: master_odex25_mobile, head: preprod_odex25_mobile } + - { base: master_odex25_pos, head: preprod_odex25_pos } + - { base: master_odex25_project, head: preprod_odex25_project } + - { base: master_odex25_purchase, head: preprod_odex25_purchase } + - { base: master_odex25_realstate, head: preprod_odex25_realstate } + - { base: master_odex25_sales, head: preprod_odex25_sales } + - { base: master_odex25_survey, head: preprod_odex25_survey } + - { base: master_odex25_transactions, head: preprod_odex25_transactions } + - { base: master_odex25_website, head: preprod_odex25_website } + - { base: master_openeducat_erp-14.0.1.0, head: preprod_openeducat_erp-14.0.1.0 } + - { base: master_odex25_ensan, head: preprod_odex25_ensan } + - { base: master_odex25_helpdesk, head: preprod_odex25_helpdesk } + - { base: master_odex25_donation, head: preprod_odex25_donation } + steps: - - name: Check source branch for merge + - name: Validate branch relationship run: | echo "Base branch: ${{ github.base_ref }}" echo "Head branch: ${{ github.head_ref }}" - - # โœ… Preprod Branches - if [[ "${{ github.base_ref }}" == "preprod_odex-event" && "${{ github.head_ref }}" != "dev_odex-event" ]]; then - echo "::error ::You can only merge to 'preprod_odex-event' from 'dev_odex-event' branch!" + if [[ "${{ github.base_ref }}" == "${{ matrix.base_head.base }}" && "${{ github.head_ref }}" != "${{ matrix.base_head.head }}" ]]; then + echo "::error ::You can only merge to '${{ matrix.base_head.base }}' from '${{ matrix.base_head.head }}'!" exit 1 fi - if [[ "${{ github.base_ref }}" == "preprod_odex25_accounting" && "${{ github.head_ref }}" != "dev_odex25_accounting" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_accounting' from 'dev_odex25_accounting' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_base" && "${{ github.head_ref }}" != "dev_odex25_base" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_base' from 'dev_odex25_base' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_dms" && "${{ github.head_ref }}" != "dev_odex25_dms" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_dms' from 'dev_odex25_dms' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_fleet" && "${{ github.head_ref }}" != "dev_odex25_fleet" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_fleet' from 'dev_odex25_fleet' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_hr" && "${{ github.head_ref }}" != "dev_odex25_hr" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_hr' from 'dev_odex25_hr' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_inventory" && "${{ github.head_ref }}" != "dev_odex25_inventory" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_inventory' from 'dev_odex25_inventory' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_maintenance" && "${{ github.head_ref }}" != "dev_odex25_maintenance" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_maintenance' from 'dev_odex25_maintenance' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_mobile" && "${{ github.head_ref }}" != "dev_odex25_mobile" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_mobile' from 'dev_odex25_mobile' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_pos" && "${{ github.head_ref }}" != "dev_odex25_pos" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_pos' from 'dev_odex25_pos' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_project" && "${{ github.head_ref }}" != "dev_odex25_project" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_project' from 'dev_odex25_project' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_purchase" && "${{ github.head_ref }}" != "dev_odex25_purchase" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_purchase' from 'dev_odex25_purchase' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_realstate" && "${{ github.head_ref }}" != "dev_odex25_realstate" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_realstate' from 'dev_odex25_realstate' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_sales" && "${{ github.head_ref }}" != "dev_odex25_sales" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_sales' from 'dev_odex25_sales' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_survey" && "${{ github.head_ref }}" != "dev_odex25_survey" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_survey' from 'dev_odex25_survey' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_transactions" && "${{ github.head_ref }}" != "dev_odex25_transactions" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_transactions' from 'dev_odex25_transactions' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_website" && "${{ github.head_ref }}" != "dev_odex25_website" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_website' from 'dev_odex25_website' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_openeducat_erp-14.0.1.0" && "${{ github.head_ref }}" != "dev_openeducat_erp-14.0.1.0" ]]; then - echo "::error ::You can only merge to 'preprod_openeducat_erp-14.0.1.0' from 'dev_openeducat_erp-14.0.1.0' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_ensan" && "${{ github.head_ref }}" != "dev_odex25_ensan" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_ensan' from 'dev_odex25_ensan' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_helpdesk" && "${{ github.head_ref }}" != "dev_odex25_helpdesk" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_helpdesk' from 'dev_odex25_helpdesk' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "preprod_odex25_donation" && "${{ github.head_ref }}" != "dev_odex25_donation" ]]; then - echo "::error ::You can only merge to 'preprod_odex25_donation' from 'dev_odex25_donation' branch!" - exit 1 - fi - - # โœ… Master Branches - if [[ "${{ github.base_ref }}" == "master_odex-event" && "${{ github.head_ref }}" != "preprod_odex-event" ]]; then - echo "::error ::You can only merge to 'master_odex-event' from 'preprod_odex-event' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_accounting" && "${{ github.head_ref }}" != "preprod_odex25_accounting" ]]; then - echo "::error ::You can only merge to 'master_odex25_accounting' from 'preprod_odex25_accounting' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_base" && "${{ github.head_ref }}" != "preprod_odex25_base" ]]; then - echo "::error ::You can only merge to 'master_odex25_base' from 'preprod_odex25_base' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_dms" && "${{ github.head_ref }}" != "preprod_odex25_dms" ]]; then - echo "::error ::You can only merge to 'master_odex25_dms' from 'preprod_odex25_dms' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_fleet" && "${{ github.head_ref }}" != "preprod_odex25_fleet" ]]; then - echo "::error ::You can only merge to 'master_odex25_fleet' from 'preprod_odex25_fleet' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_hr" && "${{ github.head_ref }}" != "preprod_odex25_hr" ]]; then - echo "::error ::You can only merge to 'master_odex25_hr' from 'preprod_odex25_hr' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_inventory" && "${{ github.head_ref }}" != "preprod_odex25_inventory" ]]; then - echo "::error ::You can only merge to 'master_odex25_inventory' from 'preprod_odex25_inventory' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_maintenance" && "${{ github.head_ref }}" != "preprod_odex25_maintenance" ]]; then - echo "::error ::You can only merge to 'master_odex25_maintenance' from 'preprod_odex25_maintenance' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_mobile" && "${{ github.head_ref }}" != "preprod_odex25_mobile" ]]; then - echo "::error ::You can only merge to 'master_odex25_mobile' from 'preprod_odex25_mobile' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_pos" && "${{ github.head_ref }}" != "preprod_odex25_pos" ]]; then - echo "::error ::You can only merge to 'master_odex25_pos' from 'preprod_odex25_pos' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_project" && "${{ github.head_ref }}" != "preprod_odex25_project" ]]; then - echo "::error ::You can only merge to 'master_odex25_project' from 'preprod_odex25_project' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_purchase" && "${{ github.head_ref }}" != "preprod_odex25_purchase" ]]; then - echo "::error ::You can only merge to 'master_odex25_purchase' from 'preprod_odex25_purchase' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_realstate" && "${{ github.head_ref }}" != "preprod_odex25_realstate" ]]; then - echo "::error ::You can only merge to 'master_odex25_realstate' from 'preprod_odex25_realstate' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_sales" && "${{ github.head_ref }}" != "preprod_odex25_sales" ]]; then - echo "::error ::You can only merge to 'master_odex25_sales' from 'preprod_odex25_sales' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_survey" && "${{ github.head_ref }}" != "preprod_odex25_survey" ]]; then - echo "::error ::You can only merge to 'master_odex25_survey' from 'preprod_odex25_survey' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_transactions" && "${{ github.head_ref }}" != "preprod_odex25_transactions" ]]; then - echo "::error ::You can only merge to 'master_odex25_transactions' from 'preprod_odex25_transactions' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_website" && "${{ github.head_ref }}" != "preprod_odex25_website" ]]; then - echo "::error ::You can only merge to 'master_odex25_website' from 'preprod_odex25_website' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_openeducat_erp-14.0.1.0" && "${{ github.head_ref }}" != "preprod_openeducat_erp-14.0.1.0" ]]; then - echo "::error ::You can only merge to 'master_openeducat_erp-14.0.1.0' from 'preprod_openeducat_erp-14.0.1.0' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_ensan" && "${{ github.head_ref }}" != "preprod_odex25_ensan" ]]; then - echo "::error ::You can only merge to 'master_odex25_ensan' from 'preprod_odex25_ensan' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_helpdesk" && "${{ github.head_ref }}" != "preprod_odex25_helpdesk" ]]; then - echo "::error ::You can only merge to 'master_odex25_helpdesk' from 'preprod_odex25_helpdesk' branch!" - exit 1 - fi - - if [[ "${{ github.base_ref }}" == "master_odex25_donation" && "${{ github.head_ref }}" != "preprod_odex25_donation" ]]; then - echo "::error ::You can only merge to 'master_odex25_donation' from 'preprod_odex25_donation' branch!" - exit 1 - fi - - - # โŒ Block ALL merges to 'master' if [[ "${{ github.base_ref }}" == "master" ]]; then - echo "::error ::Merging to 'master' branch is not allowed!" + echo "::error ::Direct merges to 'master' are blocked!" exit 1 fi - echo "Merge validation passed." \ No newline at end of file + echo "โœ… Merge validation passed." + + lint: + runs-on: app-sever-project-runner + if: ${{ github.event.pull_request.changed_files > 0 }} + concurrency: + group: linting-${{ github.head_ref }} + cancel-in-progress: true + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run linter (example: YAML lint) + run: | + echo "Running linter only on changed files..." + # Example: yamllint . --strict + echo "โœ… Linting complete." \ No newline at end of file From 9b68b8d1753b30f03bef7ea2efc4e63296d0818e Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 17:14:37 +0300 Subject: [PATCH 03/11] Add or update prevent-invalid-branch-merges workflow --- .github/workflows/prevent-invalid-branch-merges.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prevent-invalid-branch-merges.yml b/.github/workflows/prevent-invalid-branch-merges.yml index b9303a11e..69b8b367f 100644 --- a/.github/workflows/prevent-invalid-branch-merges.yml +++ b/.github/workflows/prevent-invalid-branch-merges.yml @@ -74,7 +74,7 @@ concurrency: jobs: validate-merge-source: - runs-on: app-sever-project-runner + runs-on: linting_odex25-standard-modules_runner_app_server strategy: matrix: base_head: From 5e73178a6d91082719107fd803b9cdc05fb83d55 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 19:21:44 +0300 Subject: [PATCH 04/11] Add or update prevent-invalid-branch-merges workflow --- .../prevent-invalid-branch-merges.yml | 294 +++++++++++++----- 1 file changed, 221 insertions(+), 73 deletions(-) diff --git a/.github/workflows/prevent-invalid-branch-merges.yml b/.github/workflows/prevent-invalid-branch-merges.yml index 69b8b367f..0c1924e39 100644 --- a/.github/workflows/prevent-invalid-branch-merges.yml +++ b/.github/workflows/prevent-invalid-branch-merges.yml @@ -25,6 +25,7 @@ on: - dev_odex25_ensan - dev_odex25_helpdesk - dev_odex25_donation + - preprod_odex-event - preprod_odex25_accounting - preprod_odex25_base @@ -46,7 +47,8 @@ on: - preprod_odex25_ensan - preprod_odex25_helpdesk - preprod_odex25_donation - - master_odex-event + + - master_odex-event - master_odex25_accounting - master_odex25_base - master_odex25_dms @@ -64,93 +66,239 @@ on: - master_odex25_transactions - master_odex25_website - master_openeducat_erp-14.0.1.0 - - master_odex25_ensan + - master_odex25_ensan - master_odex25_helpdesk - master_odex25_donation - -concurrency: - group: prevent-invalid-merges-${{ github.head_ref }} - cancel-in-progress: true + + jobs: validate-merge-source: runs-on: linting_odex25-standard-modules_runner_app_server - strategy: - matrix: - base_head: - - { base: preprod_odex-event, head: dev_odex-event } - - { base: preprod_odex25_accounting, head: dev_odex25_accounting } - - { base: preprod_odex25_base, head: dev_odex25_base } - - { base: preprod_odex25_dms, head: dev_odex25_dms } - - { base: preprod_odex25_fleet, head: dev_odex25_fleet } - - { base: preprod_odex25_hr, head: dev_odex25_hr } - - { base: preprod_odex25_inventory, head: dev_odex25_inventory } - - { base: preprod_odex25_maintenance, head: dev_odex25_maintenance } - - { base: preprod_odex25_mobile, head: dev_odex25_mobile } - - { base: preprod_odex25_pos, head: dev_odex25_pos } - - { base: preprod_odex25_project, head: dev_odex25_project } - - { base: preprod_odex25_purchase, head: dev_odex25_purchase } - - { base: preprod_odex25_realstate, head: dev_odex25_realstate } - - { base: preprod_odex25_sales, head: dev_odex25_sales } - - { base: preprod_odex25_survey, head: dev_odex25_survey } - - { base: preprod_odex25_transactions, head: dev_odex25_transactions } - - { base: preprod_odex25_website, head: dev_odex25_website } - - { base: preprod_openeducat_erp-14.0.1.0, head: dev_openeducat_erp-14.0.1.0 } - - { base: preprod_odex25_ensan, head: dev_odex25_ensan } - - { base: preprod_odex25_helpdesk, head: dev_odex25_helpdesk } - - { base: preprod_odex25_donation, head: dev_odex25_donation } - - { base: master_odex-event, head: preprod_odex-event } - - { base: master_odex25_accounting, head: preprod_odex25_accounting } - - { base: master_odex25_base, head: preprod_odex25_base } - - { base: master_odex25_dms, head: preprod_odex25_dms } - - { base: master_odex25_fleet, head: preprod_odex25_fleet } - - { base: master_odex25_hr, head: preprod_odex25_hr } - - { base: master_odex25_inventory, head: preprod_odex25_inventory } - - { base: master_odex25_maintenance, head: preprod_odex25_maintenance } - - { base: master_odex25_mobile, head: preprod_odex25_mobile } - - { base: master_odex25_pos, head: preprod_odex25_pos } - - { base: master_odex25_project, head: preprod_odex25_project } - - { base: master_odex25_purchase, head: preprod_odex25_purchase } - - { base: master_odex25_realstate, head: preprod_odex25_realstate } - - { base: master_odex25_sales, head: preprod_odex25_sales } - - { base: master_odex25_survey, head: preprod_odex25_survey } - - { base: master_odex25_transactions, head: preprod_odex25_transactions } - - { base: master_odex25_website, head: preprod_odex25_website } - - { base: master_openeducat_erp-14.0.1.0, head: preprod_openeducat_erp-14.0.1.0 } - - { base: master_odex25_ensan, head: preprod_odex25_ensan } - - { base: master_odex25_helpdesk, head: preprod_odex25_helpdesk } - - { base: master_odex25_donation, head: preprod_odex25_donation } - steps: - - name: Validate branch relationship + - name: Check source branch for merge run: | echo "Base branch: ${{ github.base_ref }}" echo "Head branch: ${{ github.head_ref }}" - if [[ "${{ github.base_ref }}" == "${{ matrix.base_head.base }}" && "${{ github.head_ref }}" != "${{ matrix.base_head.head }}" ]]; then - echo "::error ::You can only merge to '${{ matrix.base_head.base }}' from '${{ matrix.base_head.head }}'!" + + # โœ… Preprod Branches + if [[ "${{ github.base_ref }}" == "preprod_odex-event" && "${{ github.head_ref }}" != "dev_odex-event" ]]; then + echo "::error ::You can only merge to 'preprod_odex-event' from 'dev_odex-event' branch!" exit 1 fi + if [[ "${{ github.base_ref }}" == "preprod_odex25_accounting" && "${{ github.head_ref }}" != "dev_odex25_accounting" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_accounting' from 'dev_odex25_accounting' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_base" && "${{ github.head_ref }}" != "dev_odex25_base" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_base' from 'dev_odex25_base' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_dms" && "${{ github.head_ref }}" != "dev_odex25_dms" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_dms' from 'dev_odex25_dms' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_fleet" && "${{ github.head_ref }}" != "dev_odex25_fleet" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_fleet' from 'dev_odex25_fleet' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_hr" && "${{ github.head_ref }}" != "dev_odex25_hr" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_hr' from 'dev_odex25_hr' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_inventory" && "${{ github.head_ref }}" != "dev_odex25_inventory" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_inventory' from 'dev_odex25_inventory' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_maintenance" && "${{ github.head_ref }}" != "dev_odex25_maintenance" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_maintenance' from 'dev_odex25_maintenance' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_mobile" && "${{ github.head_ref }}" != "dev_odex25_mobile" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_mobile' from 'dev_odex25_mobile' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_pos" && "${{ github.head_ref }}" != "dev_odex25_pos" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_pos' from 'dev_odex25_pos' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_project" && "${{ github.head_ref }}" != "dev_odex25_project" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_project' from 'dev_odex25_project' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_purchase" && "${{ github.head_ref }}" != "dev_odex25_purchase" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_purchase' from 'dev_odex25_purchase' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_realstate" && "${{ github.head_ref }}" != "dev_odex25_realstate" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_realstate' from 'dev_odex25_realstate' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_sales" && "${{ github.head_ref }}" != "dev_odex25_sales" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_sales' from 'dev_odex25_sales' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_survey" && "${{ github.head_ref }}" != "dev_odex25_survey" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_survey' from 'dev_odex25_survey' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_transactions" && "${{ github.head_ref }}" != "dev_odex25_transactions" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_transactions' from 'dev_odex25_transactions' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_website" && "${{ github.head_ref }}" != "dev_odex25_website" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_website' from 'dev_odex25_website' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_openeducat_erp-14.0.1.0" && "${{ github.head_ref }}" != "dev_openeducat_erp-14.0.1.0" ]]; then + echo "::error ::You can only merge to 'preprod_openeducat_erp-14.0.1.0' from 'dev_openeducat_erp-14.0.1.0' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_ensan" && "${{ github.head_ref }}" != "dev_odex25_ensan" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_ensan' from 'dev_odex25_ensan' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_helpdesk" && "${{ github.head_ref }}" != "dev_odex25_helpdesk" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_helpdesk' from 'dev_odex25_helpdesk' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "preprod_odex25_donation" && "${{ github.head_ref }}" != "dev_odex25_donation" ]]; then + echo "::error ::You can only merge to 'preprod_odex25_donation' from 'dev_odex25_donation' branch!" + exit 1 + fi + + # โœ… Master Branches + if [[ "${{ github.base_ref }}" == "master_odex-event" && "${{ github.head_ref }}" != "preprod_odex-event" ]]; then + echo "::error ::You can only merge to 'master_odex-event' from 'preprod_odex-event' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_accounting" && "${{ github.head_ref }}" != "preprod_odex25_accounting" ]]; then + echo "::error ::You can only merge to 'master_odex25_accounting' from 'preprod_odex25_accounting' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_base" && "${{ github.head_ref }}" != "preprod_odex25_base" ]]; then + echo "::error ::You can only merge to 'master_odex25_base' from 'preprod_odex25_base' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_dms" && "${{ github.head_ref }}" != "preprod_odex25_dms" ]]; then + echo "::error ::You can only merge to 'master_odex25_dms' from 'preprod_odex25_dms' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_fleet" && "${{ github.head_ref }}" != "preprod_odex25_fleet" ]]; then + echo "::error ::You can only merge to 'master_odex25_fleet' from 'preprod_odex25_fleet' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_hr" && "${{ github.head_ref }}" != "preprod_odex25_hr" ]]; then + echo "::error ::You can only merge to 'master_odex25_hr' from 'preprod_odex25_hr' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_inventory" && "${{ github.head_ref }}" != "preprod_odex25_inventory" ]]; then + echo "::error ::You can only merge to 'master_odex25_inventory' from 'preprod_odex25_inventory' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_maintenance" && "${{ github.head_ref }}" != "preprod_odex25_maintenance" ]]; then + echo "::error ::You can only merge to 'master_odex25_maintenance' from 'preprod_odex25_maintenance' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_mobile" && "${{ github.head_ref }}" != "preprod_odex25_mobile" ]]; then + echo "::error ::You can only merge to 'master_odex25_mobile' from 'preprod_odex25_mobile' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_pos" && "${{ github.head_ref }}" != "preprod_odex25_pos" ]]; then + echo "::error ::You can only merge to 'master_odex25_pos' from 'preprod_odex25_pos' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_project" && "${{ github.head_ref }}" != "preprod_odex25_project" ]]; then + echo "::error ::You can only merge to 'master_odex25_project' from 'preprod_odex25_project' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_purchase" && "${{ github.head_ref }}" != "preprod_odex25_purchase" ]]; then + echo "::error ::You can only merge to 'master_odex25_purchase' from 'preprod_odex25_purchase' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_realstate" && "${{ github.head_ref }}" != "preprod_odex25_realstate" ]]; then + echo "::error ::You can only merge to 'master_odex25_realstate' from 'preprod_odex25_realstate' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_sales" && "${{ github.head_ref }}" != "preprod_odex25_sales" ]]; then + echo "::error ::You can only merge to 'master_odex25_sales' from 'preprod_odex25_sales' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_survey" && "${{ github.head_ref }}" != "preprod_odex25_survey" ]]; then + echo "::error ::You can only merge to 'master_odex25_survey' from 'preprod_odex25_survey' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_transactions" && "${{ github.head_ref }}" != "preprod_odex25_transactions" ]]; then + echo "::error ::You can only merge to 'master_odex25_transactions' from 'preprod_odex25_transactions' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_website" && "${{ github.head_ref }}" != "preprod_odex25_website" ]]; then + echo "::error ::You can only merge to 'master_odex25_website' from 'preprod_odex25_website' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_openeducat_erp-14.0.1.0" && "${{ github.head_ref }}" != "preprod_openeducat_erp-14.0.1.0" ]]; then + echo "::error ::You can only merge to 'master_openeducat_erp-14.0.1.0' from 'preprod_openeducat_erp-14.0.1.0' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_ensan" && "${{ github.head_ref }}" != "preprod_odex25_ensan" ]]; then + echo "::error ::You can only merge to 'master_odex25_ensan' from 'preprod_odex25_ensan' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_helpdesk" && "${{ github.head_ref }}" != "preprod_odex25_helpdesk" ]]; then + echo "::error ::You can only merge to 'master_odex25_helpdesk' from 'preprod_odex25_helpdesk' branch!" + exit 1 + fi + + if [[ "${{ github.base_ref }}" == "master_odex25_donation" && "${{ github.head_ref }}" != "preprod_odex25_donation" ]]; then + echo "::error ::You can only merge to 'master_odex25_donation' from 'preprod_odex25_donation' branch!" + exit 1 + fi + + + # โŒ Block ALL merges to 'master' if [[ "${{ github.base_ref }}" == "master" ]]; then - echo "::error ::Direct merges to 'master' are blocked!" + echo "::error ::Merging to 'master' branch is not allowed!" exit 1 fi - echo "โœ… Merge validation passed." - - lint: - runs-on: app-sever-project-runner - if: ${{ github.event.pull_request.changed_files > 0 }} - concurrency: - group: linting-${{ github.head_ref }} - cancel-in-progress: true - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Run linter (example: YAML lint) - run: | - echo "Running linter only on changed files..." - # Example: yamllint . --strict - echo "โœ… Linting complete." \ No newline at end of file + echo "Merge validation passed." \ No newline at end of file From e3f40d897f0d4e7e8d50c4bd8c77a30fa258e389 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 20:59:27 +0300 Subject: [PATCH 05/11] Add or update delete-merged-branches workflow --- .github/workflows/delete-merged-branches.yml | 172 ++++++++++++++----- 1 file changed, 132 insertions(+), 40 deletions(-) diff --git a/.github/workflows/delete-merged-branches.yml b/.github/workflows/delete-merged-branches.yml index ff902a5c7..a0f5c88e8 100644 --- a/.github/workflows/delete-merged-branches.yml +++ b/.github/workflows/delete-merged-branches.yml @@ -1,41 +1,133 @@ # Author: Ahmad Samir - name: Auto Delete Branch After Merge - - on: - pull_request: - types: [closed] # triggers when PR is closed (merged or just closed) - - jobs: - delete-merged-branch: - if: github.event.pull_request.merged == true - name: Delete Merged Branch - runs-on: app-sever-project-runner # your self-hosted runner - - steps: - - name: Delete merged branch (with rules) - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - REPO: ${{ github.repository }} - BASE_REF: ${{ github.event.pull_request.base.ref }} - HEAD_REF: ${{ github.event.pull_request.head.ref }} - run: | - echo "๐Ÿ” Base branch: $BASE_REF" - echo "๐Ÿ” Head branch: $HEAD_REF" - - # โŒ Do not delete dev_* merged into preprod_* - if [[ "$HEAD_REF" == dev_* && "$BASE_REF" == preprod_* ]]; then - echo "๐Ÿšซ Rule: Do not delete dev_* merged into preprod_*" - exit 0 - fi - - # โŒ Do not delete preprod_* merged into master_* - if [[ "$HEAD_REF" == preprod_* && "$BASE_REF" == master_* ]]; then - echo "๐Ÿšซ Rule: Do not delete preprod_* merged into master_*" - exit 0 - fi - - echo "โœ… Allowed to delete '$HEAD_REF' from '$REPO'" - - curl -s -X DELETE -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$REPO/git/refs/heads/$HEAD_REF -w " -๐Ÿงน Deleted branch '$HEAD_REF' from '$REPO' โ€” HTTP %{http_code} -" \ No newline at end of file +name: Auto Delete Branch After Merge + +on: + pull_request: + types: [closed] + +jobs: + delete-merged-branch: + if: github.event.pull_request.merged == true + name: Delete Merged Branch + runs-on: linting_odex25-standard-modules_runner + + steps: + - name: Delete merged branch (with protection check and rules) + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + REPO: ${{ github.repository }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + run: | + echo "๐Ÿ” Base branch: $BASE_REF" + echo "๐Ÿ” Head branch: $HEAD_REF" + + PROTECTED_BRANCHES=( + master + 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 + preprod_odex-event + preprod_odex25_accounting + preprod_odex25_base + preprod_odex25_dms + preprod_odex25_fleet + preprod_odex25_hr + preprod_odex25_inventory + preprod_odex25_maintenance + preprod_odex25_mobile + preprod_odex25_pos + preprod_odex25_project + preprod_odex25_purchase + preprod_odex25_realstate + preprod_odex25_sales + preprod_odex25_survey + preprod_odex25_transactions + preprod_odex25_website + preprod_openeducat_erp-14.0.1.0 + preprod_odex25_ensan + preprod_odex25_helpdesk + preprod_odex25_donation + master_odex-event + master_odex25_accounting + master_odex25_base + master_odex25_dms + master_odex25_fleet + master_odex25_hr + master_odex25_inventory + master_odex25_maintenance + master_odex25_mobile + master_odex25_pos + master_odex25_project + master_odex25_purchase + master_odex25_realstate + master_odex25_sales + master_odex25_survey + master_odex25_transactions + master_odex25_website + master_openeducat_erp-14.0.1.0 + master_odex25_ensan + master_odex25_helpdesk + master_odex25_donation + ) + + # Rule 1 + if [[ "$HEAD_REF" == dev_* && "$BASE_REF" == preprod_* ]]; then + echo "๐Ÿšซ Rule: Do not delete dev_* merged into preprod_*" + exit 0 + fi + + # Rule 2 + if [[ "$HEAD_REF" == preprod_* && "$BASE_REF" == master_* ]]; then + echo "๐Ÿšซ Rule: Do not delete preprod_* merged into master_*" + exit 0 + fi + + # Rule 3: Protected branches + for protected in "${PROTECTED_BRANCHES[@]}"; do + if [[ "$HEAD_REF" == "$protected" ]]; then + echo "๐Ÿ›ก๏ธ '$HEAD_REF' is a protected branch. Skipping deletion." + exit 0 + fi + done + + echo "โœ… '$HEAD_REF' is eligible for deletion. Checking protection..." + + PROTECTION_URL="https://api.github.com/repos/$REPO/branches/$HEAD_REF/protection" + + PROTECTION_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GH_TOKEN" "$PROTECTION_URL") + + if [ "$PROTECTION_STATUS" -eq 200 ]; then + echo "๐Ÿ”“ Removing protection on '$HEAD_REF'..." + curl -s -X DELETE -H "Authorization: token $GH_TOKEN" "$PROTECTION_URL" + else + echo "โ„น๏ธ No protection found for '$HEAD_REF' (HTTP $PROTECTION_STATUS)" + fi + + echo "๐Ÿงน Attempting to delete branch '$HEAD_REF'..." + DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$REPO/git/refs/heads/$HEAD_REF) + + if [ "$DELETE_STATUS" -eq 204 ]; then + echo "โœ… Branch '$HEAD_REF' successfully deleted" + else + echo "โŒ Failed to delete branch '$HEAD_REF' โ€” HTTP $DELETE_STATUS" + exit 1 + fi \ No newline at end of file From a3c6da2217700df20a6550eae1786b438c3be3dd Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 22:49:49 +0300 Subject: [PATCH 07/11] Add or update delete-merged-branches workflow --- .github/workflows/delete-merged-branches.yml | 131 +++++++------------ 1 file changed, 50 insertions(+), 81 deletions(-) diff --git a/.github/workflows/delete-merged-branches.yml b/.github/workflows/delete-merged-branches.yml index a0f5c88e8..195dee176 100644 --- a/.github/workflows/delete-merged-branches.yml +++ b/.github/workflows/delete-merged-branches.yml @@ -1,34 +1,30 @@ # Author: Ahmad Samir -name: Auto Delete Branch After Merge +name: Block Reserved Branches on: - pull_request: - types: [closed] + create: + branches: + - '**' jobs: - delete-merged-branch: - if: github.event.pull_request.merged == true - name: Delete Merged Branch - runs-on: linting_odex25-standard-modules_runner - + block-reserved-branches: + runs-on: app-sever-project-runner steps: - - name: Delete merged branch (with protection check and rules) + - name: Check for reserved or pattern-matching branch names env: GH_TOKEN: ${{ secrets.GH_TOKEN }} REPO: ${{ github.repository }} - BASE_REF: ${{ github.event.pull_request.base.ref }} - HEAD_REF: ${{ github.event.pull_request.head.ref }} + BRANCH_NAME: ${{ github.ref_name }} run: | - echo "๐Ÿ” Base branch: $BASE_REF" - echo "๐Ÿ” Head branch: $HEAD_REF" - - PROTECTED_BRANCHES=( + RESERVED_NAMES=( master - dev_odex-event dev_odex25_accounting dev_odex25_base dev_odex25_dms + dev_odex25_donation + dev_odex25_ensan dev_odex25_fleet + dev_odex25_helpdesk dev_odex25_hr dev_odex25_inventory dev_odex25_maintenance @@ -41,36 +37,15 @@ jobs: dev_odex25_survey dev_odex25_transactions dev_odex25_website + dev_odex-event dev_openeducat_erp-14.0.1.0 - dev_odex25_ensan - dev_odex25_helpdesk - dev_odex25_donation - preprod_odex-event - preprod_odex25_accounting - preprod_odex25_base - preprod_odex25_dms - preprod_odex25_fleet - preprod_odex25_hr - preprod_odex25_inventory - preprod_odex25_maintenance - preprod_odex25_mobile - preprod_odex25_pos - preprod_odex25_project - preprod_odex25_purchase - preprod_odex25_realstate - preprod_odex25_sales - preprod_odex25_survey - preprod_odex25_transactions - preprod_odex25_website - preprod_openeducat_erp-14.0.1.0 - preprod_odex25_ensan - preprod_odex25_helpdesk - preprod_odex25_donation - master_odex-event master_odex25_accounting master_odex25_base master_odex25_dms + master_odex25_donation + master_odex25_ensan master_odex25_fleet + master_odex25_helpdesk master_odex25_hr master_odex25_inventory master_odex25_maintenance @@ -83,51 +58,45 @@ jobs: master_odex25_survey master_odex25_transactions master_odex25_website + master_odex-event master_openeducat_erp-14.0.1.0 - master_odex25_ensan - master_odex25_helpdesk - master_odex25_donation + preprod_odex25_accounting + preprod_odex25_base + preprod_odex25_dms + preprod_odex25_donation + preprod_odex25_ensan + preprod_odex25_fleet + preprod_odex25_helpdesk + preprod_odex25_hr + preprod_odex25_inventory + preprod_odex25_maintenance + preprod_odex25_mobile + preprod_odex25_pos + preprod_odex25_project + preprod_odex25_purchase + preprod_odex25_realstate + preprod_odex25_sales + preprod_odex25_survey + preprod_odex25_transactions + preprod_odex25_website + preprod_odex-event + preprod_openeducat_erp-14.0.1.0 ) - # Rule 1 - if [[ "$HEAD_REF" == dev_* && "$BASE_REF" == preprod_* ]]; then - echo "๐Ÿšซ Rule: Do not delete dev_* merged into preprod_*" - exit 0 - fi - - # Rule 2 - if [[ "$HEAD_REF" == preprod_* && "$BASE_REF" == master_* ]]; then - echo "๐Ÿšซ Rule: Do not delete preprod_* merged into master_*" - exit 0 - fi - - # Rule 3: Protected branches - for protected in "${PROTECTED_BRANCHES[@]}"; do - if [[ "$HEAD_REF" == "$protected" ]]; then - echo "๐Ÿ›ก๏ธ '$HEAD_REF' is a protected branch. Skipping deletion." - exit 0 + # Check if branch is an exact reserved name + 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 + exit 1 fi done - echo "โœ… '$HEAD_REF' is eligible for deletion. Checking protection..." - - PROTECTION_URL="https://api.github.com/repos/$REPO/branches/$HEAD_REF/protection" - - PROTECTION_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GH_TOKEN" "$PROTECTION_URL") - - if [ "$PROTECTION_STATUS" -eq 200 ]; then - echo "๐Ÿ”“ Removing protection on '$HEAD_REF'..." - curl -s -X DELETE -H "Authorization: token $GH_TOKEN" "$PROTECTION_URL" - else - echo "โ„น๏ธ No protection found for '$HEAD_REF' (HTTP $PROTECTION_STATUS)" + # Check if branch name matches restricted patterns + 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 + exit 1 fi - echo "๐Ÿงน Attempting to delete branch '$HEAD_REF'..." - DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$REPO/git/refs/heads/$HEAD_REF) - - if [ "$DELETE_STATUS" -eq 204 ]; then - echo "โœ… Branch '$HEAD_REF' successfully deleted" - else - echo "โŒ Failed to delete branch '$HEAD_REF' โ€” HTTP $DELETE_STATUS" - exit 1 - fi \ No newline at end of file + echo "โœ… Branch '$BRANCH_NAME' is allowed." \ No newline at end of file From b991fc1c831ced702482ea43bcf0ac5a5c68a2c7 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 23:09:49 +0300 Subject: [PATCH 08/11] Add or update delete-merged-branches workflow --- .github/workflows/delete-merged-branches.yml | 131 ++++++++++++------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/.github/workflows/delete-merged-branches.yml b/.github/workflows/delete-merged-branches.yml index 195dee176..a0f5c88e8 100644 --- a/.github/workflows/delete-merged-branches.yml +++ b/.github/workflows/delete-merged-branches.yml @@ -1,30 +1,34 @@ # Author: Ahmad Samir -name: Block Reserved Branches +name: Auto Delete Branch After Merge on: - create: - branches: - - '**' + pull_request: + types: [closed] jobs: - block-reserved-branches: - runs-on: app-sever-project-runner + delete-merged-branch: + if: github.event.pull_request.merged == true + name: Delete Merged Branch + runs-on: linting_odex25-standard-modules_runner + steps: - - name: Check for reserved or pattern-matching branch names + - name: Delete merged branch (with protection check and rules) env: GH_TOKEN: ${{ secrets.GH_TOKEN }} REPO: ${{ github.repository }} - BRANCH_NAME: ${{ github.ref_name }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | - RESERVED_NAMES=( + echo "๐Ÿ” Base branch: $BASE_REF" + echo "๐Ÿ” Head branch: $HEAD_REF" + + PROTECTED_BRANCHES=( master + dev_odex-event dev_odex25_accounting dev_odex25_base dev_odex25_dms - dev_odex25_donation - dev_odex25_ensan dev_odex25_fleet - dev_odex25_helpdesk dev_odex25_hr dev_odex25_inventory dev_odex25_maintenance @@ -37,36 +41,15 @@ jobs: dev_odex25_survey dev_odex25_transactions dev_odex25_website - dev_odex-event dev_openeducat_erp-14.0.1.0 - master_odex25_accounting - master_odex25_base - master_odex25_dms - master_odex25_donation - master_odex25_ensan - master_odex25_fleet - master_odex25_helpdesk - master_odex25_hr - master_odex25_inventory - master_odex25_maintenance - master_odex25_mobile - master_odex25_pos - master_odex25_project - master_odex25_purchase - master_odex25_realstate - master_odex25_sales - master_odex25_survey - master_odex25_transactions - master_odex25_website - master_odex-event - master_openeducat_erp-14.0.1.0 + dev_odex25_ensan + dev_odex25_helpdesk + dev_odex25_donation + preprod_odex-event preprod_odex25_accounting preprod_odex25_base preprod_odex25_dms - preprod_odex25_donation - preprod_odex25_ensan preprod_odex25_fleet - preprod_odex25_helpdesk preprod_odex25_hr preprod_odex25_inventory preprod_odex25_maintenance @@ -79,24 +62,72 @@ jobs: preprod_odex25_survey preprod_odex25_transactions preprod_odex25_website - preprod_odex-event preprod_openeducat_erp-14.0.1.0 + preprod_odex25_ensan + preprod_odex25_helpdesk + preprod_odex25_donation + master_odex-event + master_odex25_accounting + master_odex25_base + master_odex25_dms + master_odex25_fleet + master_odex25_hr + master_odex25_inventory + master_odex25_maintenance + master_odex25_mobile + master_odex25_pos + master_odex25_project + master_odex25_purchase + master_odex25_realstate + master_odex25_sales + master_odex25_survey + master_odex25_transactions + master_odex25_website + master_openeducat_erp-14.0.1.0 + master_odex25_ensan + master_odex25_helpdesk + master_odex25_donation ) - # Check if branch is an exact reserved name - 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 - exit 1 + # Rule 1 + if [[ "$HEAD_REF" == dev_* && "$BASE_REF" == preprod_* ]]; then + echo "๐Ÿšซ Rule: Do not delete dev_* merged into preprod_*" + exit 0 + fi + + # Rule 2 + if [[ "$HEAD_REF" == preprod_* && "$BASE_REF" == master_* ]]; then + echo "๐Ÿšซ Rule: Do not delete preprod_* merged into master_*" + exit 0 + fi + + # Rule 3: Protected branches + for protected in "${PROTECTED_BRANCHES[@]}"; do + if [[ "$HEAD_REF" == "$protected" ]]; then + echo "๐Ÿ›ก๏ธ '$HEAD_REF' is a protected branch. Skipping deletion." + exit 0 fi done - # Check if branch name matches restricted patterns - 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 - exit 1 + echo "โœ… '$HEAD_REF' is eligible for deletion. Checking protection..." + + PROTECTION_URL="https://api.github.com/repos/$REPO/branches/$HEAD_REF/protection" + + PROTECTION_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GH_TOKEN" "$PROTECTION_URL") + + if [ "$PROTECTION_STATUS" -eq 200 ]; then + echo "๐Ÿ”“ Removing protection on '$HEAD_REF'..." + curl -s -X DELETE -H "Authorization: token $GH_TOKEN" "$PROTECTION_URL" + else + echo "โ„น๏ธ No protection found for '$HEAD_REF' (HTTP $PROTECTION_STATUS)" fi - echo "โœ… Branch '$BRANCH_NAME' is allowed." \ No newline at end of file + echo "๐Ÿงน Attempting to delete branch '$HEAD_REF'..." + DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$REPO/git/refs/heads/$HEAD_REF) + + if [ "$DELETE_STATUS" -eq 204 ]; then + echo "โœ… Branch '$HEAD_REF' successfully deleted" + else + echo "โŒ Failed to delete branch '$HEAD_REF' โ€” HTTP $DELETE_STATUS" + exit 1 + fi \ No newline at end of file From e11c91f49e567a134afeb6d3f1a7b7b83c815fc6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 23:16:25 +0300 Subject: [PATCH 09/11] Add or update block_reserved_branches workflow --- .github/workflows/block_reserved_branches.yml | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/block_reserved_branches.yml diff --git a/.github/workflows/block_reserved_branches.yml b/.github/workflows/block_reserved_branches.yml new file mode 100644 index 000000000..308ba4805 --- /dev/null +++ b/.github/workflows/block_reserved_branches.yml @@ -0,0 +1,99 @@ +# Author: Ahmad Samir +name: Block Reserved Branches + +on: + create: + branches: + - '**' + +jobs: + block-reserved-branches: + runs-on: app-sever-project-runner + steps: + - name: Check for reserved or pattern-matching branch names + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + REPO: ${{ github.repository }} + BRANCH_NAME: ${{ github.ref_name }} + run: | + RESERVED_NAMES=( + master + dev_odex25_accounting + dev_odex25_base + dev_odex25_dms + dev_odex25_donation + dev_odex25_ensan + dev_odex25_fleet + dev_odex25_helpdesk + 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_odex-event + dev_openeducat_erp-14.0.1.0 + master_odex25_accounting + master_odex25_base + master_odex25_dms + master_odex25_donation + master_odex25_ensan + master_odex25_fleet + master_odex25_helpdesk + master_odex25_hr + master_odex25_inventory + master_odex25_maintenance + master_odex25_mobile + master_odex25_pos + master_odex25_project + master_odex25_purchase + master_odex25_realstate + master_odex25_sales + master_odex25_survey + master_odex25_transactions + master_odex25_website + master_odex-event + master_openeducat_erp-14.0.1.0 + preprod_odex25_accounting + preprod_odex25_base + preprod_odex25_dms + preprod_odex25_donation + preprod_odex25_ensan + preprod_odex25_fleet + preprod_odex25_helpdesk + preprod_odex25_hr + preprod_odex25_inventory + preprod_odex25_maintenance + preprod_odex25_mobile + preprod_odex25_pos + preprod_odex25_project + preprod_odex25_purchase + preprod_odex25_realstate + preprod_odex25_sales + preprod_odex25_survey + preprod_odex25_transactions + preprod_odex25_website + preprod_odex-event + preprod_openeducat_erp-14.0.1.0 + ) + # Check if branch is an exact reserved name + 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 + exit 1 + fi + done + # Check if branch name matches restricted patterns + 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 + exit 1 + fi + echo "โœ… Branch '$BRANCH_NAME' is allowed." \ No newline at end of file From 3a9889464d930b2abac7ee1c7624138d4a70fc88 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 10 Jul 2025 23:25:39 +0300 Subject: [PATCH 10/11] Add or update prevent-invalid-branch-merges workflow --- .github/workflows/prevent-invalid-branch-merges.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prevent-invalid-branch-merges.yml b/.github/workflows/prevent-invalid-branch-merges.yml index 0c1924e39..c0b8c6b63 100644 --- a/.github/workflows/prevent-invalid-branch-merges.yml +++ b/.github/workflows/prevent-invalid-branch-merges.yml @@ -74,7 +74,7 @@ on: jobs: validate-merge-source: - runs-on: linting_odex25-standard-modules_runner_app_server + runs-on: linting_odex25-standard-modules_runner steps: - name: Check source branch for merge run: | From b18b229f1213e240c4b3a3254b41959801181f58 Mon Sep 17 00:00:00 2001 From: Abdurrahman Saber Date: Sun, 13 Jul 2025 09:46:41 +0300 Subject: [PATCH 11/11] [IMP] ensan_sale_management: differentiate between first time and other donations, link new partner to his old donations --- .../models/res_partner_inherit.py | 7 ++-- .../ensan_sale_management/__manifest__.py | 2 +- .../ensan_sale_management/models/__init__.py | 2 + .../models/phone_validation_mixin.py | 17 ++++++++ .../models/res_partner.py | 18 ++++++++ .../models/sale_order.py | 42 +++++++------------ .../models/sale_report.py | 4 +- .../views/sale_order_views.xml | 1 + 8 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 odex25_donation/ensan_sale_management/models/phone_validation_mixin.py create mode 100644 odex25_donation/ensan_sale_management/models/res_partner.py diff --git a/odex25_donation/affiliate_management/models/res_partner_inherit.py b/odex25_donation/affiliate_management/models/res_partner_inherit.py index 11756a13b..f9b43a7e5 100644 --- a/odex25_donation/affiliate_management/models/res_partner_inherit.py +++ b/odex25_donation/affiliate_management/models/res_partner_inherit.py @@ -21,13 +21,11 @@ from odoo.exceptions import UserError import logging _logger = logging.getLogger(__name__) class ResPartnerInherit(models.Model): - _inherit = 'res.partner' - _description = "ResPartner Inherit Model" is_affiliate = fields.Boolean(default=False) res_affiliate_key = fields.Char(string="Affiliate key") - affiliate_program_id = fields.Many2one("affiliate.program", string="Program") + affiliate_program_id = fields.Many2one("affiliate.program", string="Program", groups="affiliate_management.affiliate_security_user_group") pending_amt = fields.Float(compute='_compute_pending_amt', string='Pending Amount') approved_amt = fields.Float(compute='_compute_approved_amt', string='Approved Amount') @@ -64,7 +62,8 @@ class ResPartnerInherit(models.Model): aff_prgm = None new_res_partner = None for vals in vals_list: - aff_prgm = self.env['affiliate.program'].search([])[-1].id if len(self.env['affiliate.program'].search([])) > 0 else '' + programs = self.env['affiliate.program'].sudo().search([]) + aff_prgm = programs[-1].id if len(programs) > 0 else '' if vals.get('is_affiliate'): vals.update({ 'affiliate_program_id': aff_prgm diff --git a/odex25_donation/ensan_sale_management/__manifest__.py b/odex25_donation/ensan_sale_management/__manifest__.py index d2333d778..86ad5a042 100644 --- a/odex25_donation/ensan_sale_management/__manifest__.py +++ b/odex25_donation/ensan_sale_management/__manifest__.py @@ -7,7 +7,7 @@ 'website': 'https://ensan.com', 'license': 'LGPL-3', 'category': 'Sales', - 'depends': ['base', 'website_sale', 'sms'], + 'depends': ['base', 'website_sale', 'sms', 'phone_validation'], 'data': [ 'security/ir.model.access.csv', 'data/sms_data.xml', diff --git a/odex25_donation/ensan_sale_management/models/__init__.py b/odex25_donation/ensan_sale_management/models/__init__.py index 482086164..e05101533 100644 --- a/odex25_donation/ensan_sale_management/models/__init__.py +++ b/odex25_donation/ensan_sale_management/models/__init__.py @@ -4,3 +4,5 @@ from . import sale_report from . import res_config_settings from . import sms_composer from . import website +from . import res_partner +from . import phone_validation_mixin diff --git a/odex25_donation/ensan_sale_management/models/phone_validation_mixin.py b/odex25_donation/ensan_sale_management/models/phone_validation_mixin.py new file mode 100644 index 000000000..88b470e3b --- /dev/null +++ b/odex25_donation/ensan_sale_management/models/phone_validation_mixin.py @@ -0,0 +1,17 @@ +from odoo import models +from odoo.addons.phone_validation.tools import phone_validation + +class PhoneValidationMixin(models.AbstractModel): + _inherit = 'phone.validation.mixin' + + def phone_format(self, number, country=None, company=None): + country = country or self._phone_get_country() + if not country: + return number + return phone_validation.phone_format( + number, + country.code if country else None, + country.phone_code if country else None, + force_format='E164', + raise_exception=False + ).replace('+', '') \ No newline at end of file diff --git a/odex25_donation/ensan_sale_management/models/res_partner.py b/odex25_donation/ensan_sale_management/models/res_partner.py new file mode 100644 index 000000000..cd2352e13 --- /dev/null +++ b/odex25_donation/ensan_sale_management/models/res_partner.py @@ -0,0 +1,18 @@ +from odoo import models, fields, api +from odoo.addons.phone_validation.tools import phone_validation + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + order_ids = self.env['sale.order'].search([]) + portal_user_id = self.env.ref('base.public_partner').id + for rec in res: + phone = rec.phone or rec.mobile + partner_orders = order_ids.filtered(lambda o: o.order_mobile_number == phone and o.partner_id.id == portal_user_id) + partner_orders.write({'partner_id': rec.id}) + return res + diff --git a/odex25_donation/ensan_sale_management/models/sale_order.py b/odex25_donation/ensan_sale_management/models/sale_order.py index cf3676764..af69918a8 100644 --- a/odex25_donation/ensan_sale_management/models/sale_order.py +++ b/odex25_donation/ensan_sale_management/models/sale_order.py @@ -1,34 +1,11 @@ from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError from odoo.tools.json import scriptsafe as json_scriptsafe - -def check_mobile_number_validation(phone): - if phone[0] == '+' and phone[1] != '0': - if phone[1:4] == '966': - if len(phone[4:]) >= 8: - return phone - else: - phone = phone[0] + '966' + phone[1:] - elif phone[0] == '+' and phone[1] == '0': - phone = phone[0] + '966' + phone[2:] - elif phone[0] == '0' and phone[1] == '5': - phone = '+966' + phone[1:] - elif phone[0:2] == '00': # 00966555555555 - if phone[2:5] == '966': - phone = '+' + '966' + phone[5:] - elif phone[0] == '0': # 0966555555555 - if phone[1:4] == '966': - phone = '+' + '966' + phone[4:] - else: - if phone[0:3] == '966': - phone = '+' + phone - else: - phone = '+' + '966' + phone - return phone - +from odoo.addons.phone_validation.tools import phone_validation class SaleOrder(models.Model): - _inherit = 'sale.order' + _name = 'sale.order' + _inherit = ['sale.order', 'phone.validation.mixin'] done_with_quick_donation = fields.Boolean(default=False) order_mobile_number = fields.Char("Donor Number") @@ -39,7 +16,18 @@ class SaleOrder(models.Model): first_receiver_number = fields.Char("Receiver Number", compute="_compute_receiver_details", store=True) first_receiver_name = fields.Char("Receiver Name", compute="_compute_receiver_details", store=True) cart_recovery_sms_sent = fields.Boolean('Cart recovery sms already sent') + is_first_time = fields.Boolean('First Time Donation') + def write(self, vals): + if 'order_mobile_number' in vals: + vals['order_mobile_number'] = self.phone_format(vals['order_mobile_number']) + numbers = set(self.search([('state', '=', 'sale'), ('id', 'not in', self.ids)]).mapped('order_mobile_number')) + if vals['order_mobile_number'] in numbers: + vals['is_first_time'] = False + else: + vals['is_first_time'] = True + return super().write(vals) + def get_sale_order_portal_url(self): for sale in self: sale.sale_order_portal_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + sale.get_portal_url() @@ -69,7 +57,7 @@ class SaleOrder(models.Model): 'product_id': int(i.get('product_id')), 'donated_amount': float(i.get('donated_amount')), 'donator_name': i.get('donator_name'), - 'donator_mobile_number': check_mobile_number_validation(i.get('donator_mobile_number')).replace('+', '') + 'donator_mobile_number': self.phone_format(i.get('donator_mobile_number')) })) gift_sender_name = i.get("gift_sender_name") gift_sender_mobile = i.get("gift_sender_mobile") diff --git a/odex25_donation/ensan_sale_management/models/sale_report.py b/odex25_donation/ensan_sale_management/models/sale_report.py index 22d8ba215..887851922 100644 --- a/odex25_donation/ensan_sale_management/models/sale_report.py +++ b/odex25_donation/ensan_sale_management/models/sale_report.py @@ -10,6 +10,7 @@ class SaleReport(models.Model): first_receiver_name = fields.Char("Receiver Name", readonly=True) order_mobile_number = fields.Char("Donor Number", readonly=True) order_name = fields.Char("Donor Name", readonly=True) + is_first_time = fields.Boolean("First Time Donation", readonly=True) def _select_additional_fields(self, fields): fields['is_gift'] = ", s.is_gift" @@ -17,10 +18,11 @@ class SaleReport(models.Model): fields['first_receiver_name'] = ", s.first_receiver_name" fields['order_mobile_number'] = ", s.order_mobile_number" fields['order_name'] = ", s.order_name" + fields['is_first_time'] = ", s.is_first_time" return super()._select_additional_fields(fields) def _group_by_sale(self, groupby=''): res = super()._group_by_sale(groupby) - res += """, s.is_gift, s.first_receiver_number,s.first_receiver_name,s.order_mobile_number,s.order_name""" + res += """, s.is_gift, s.first_receiver_number,s.first_receiver_name,s.order_mobile_number,s.order_name,s.is_first_time""" return res \ No newline at end of file diff --git a/odex25_donation/ensan_sale_management/views/sale_order_views.xml b/odex25_donation/ensan_sale_management/views/sale_order_views.xml index cbf527405..b95de3dde 100644 --- a/odex25_donation/ensan_sale_management/views/sale_order_views.xml +++ b/odex25_donation/ensan_sale_management/views/sale_order_views.xml @@ -9,6 +9,7 @@ +