diff --git a/.github/workflows/deploy-pipeline.yml b/.github/workflows/deploy-pipeline.yml index 14b8abf..57a0ab3 100644 --- a/.github/workflows/deploy-pipeline.yml +++ b/.github/workflows/deploy-pipeline.yml @@ -13,7 +13,7 @@ env: IMAGE_NAME_VALUE_2: openems-backend # IMAGE_NAME_VALUE_3: openems-db IMAGE_NAME_VALUE_4: odoo - IMAGE_NAME_VALUE_5: odoo-db + # IMAGE_NAME_VALUE_5: odoo-db IMAGE_NAME_VALUE_6: openems-edge TERRAFORM_ACTION: apply ECS_TD: .github/workflows/openems-deployment-td.json @@ -71,7 +71,7 @@ jobs: - name: Check if ECR repositories exist env: # List your repository names here - REPO_NAMES: '${{ env.IMAGE_NAME_VALUE_1 }},${{ env.IMAGE_NAME_VALUE_2 }},${{ env.IMAGE_NAME_VALUE_4 }},${{ env.IMAGE_NAME_VALUE_5 }},${{ env.IMAGE_NAME_VALUE_6 }}' + REPO_NAMES: '${{ env.IMAGE_NAME_VALUE_1 }},${{ env.IMAGE_NAME_VALUE_2 }},${{ env.IMAGE_NAME_VALUE_4 }},${{ env.IMAGE_NAME_VALUE_6 }}' run: | IFS=',' read -ra REPOS <<< "$REPO_NAMES" for repo in "${REPOS[@]}"; do @@ -89,7 +89,7 @@ jobs: - name: Create ECR repositories env: # List your repository names here - REPO_NAMES: '${{ env.IMAGE_NAME_VALUE_1 }},${{ env.IMAGE_NAME_VALUE_2 }},${{ env.IMAGE_NAME_VALUE_4 }},${{ env.IMAGE_NAME_VALUE_5 }},${{ env.IMAGE_NAME_VALUE_6 }}' + REPO_NAMES: '${{ env.IMAGE_NAME_VALUE_1 }},${{ env.IMAGE_NAME_VALUE_2 }},${{ env.IMAGE_NAME_VALUE_4 }},${{ env.IMAGE_NAME_VALUE_6 }}' run: | IFS=',' read -ra REPOS <<< "$REPO_NAMES" for repo in "${REPOS[@]}"; do @@ -123,9 +123,17 @@ jobs: - name: Login to Amazon ECR uses: aws-actions/amazon-ecr-login@v1 + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v2 + + # - name: Install Docker Compose + # run: | + # curl -L "https://github.com/docker/compose/releases/download/2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + # chmod +x /usr/local/bin/docker-compose + - name: Build an image from Docker Compose run: | - docker-compose build + docker compose build - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master @@ -155,12 +163,12 @@ jobs: format: 'sarif' output: 'trivy-results.sarif' - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_5 }}:latest' - format: 'sarif' - output: 'trivy-results.sarif' + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@master + # with: + # image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_5 }}:latest' + # format: 'sarif' + # output: 'trivy-results.sarif' - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master @@ -223,19 +231,19 @@ jobs: container-name: openems-deployment-container-odoo image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_4 }}:latest - - name: Modify Amazon ECS task definition with forth container - id: render-odoo-db-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ steps.render-odoo-container.outputs.task-definition }} - container-name: openems-deployment-container-odoo-db - image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_4 }}:latest + # - name: Modify Amazon ECS task definition with forth container + # id: render-odoo-db-container + # uses: aws-actions/amazon-ecs-render-task-definition@v1 + # with: + # task-definition: ${{ steps.render-odoo-container.outputs.task-definition }} + # container-name: openems-deployment-container-odoo-db + # image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_4 }}:latest - name: Modify Amazon ECS task definition with fifth container id: render-edge-container uses: aws-actions/amazon-ecs-render-task-definition@v1 with: - task-definition: ${{ steps.render-odoo-db-container.outputs.task-definition }} + task-definition: ${{ steps.render-odoo-container.outputs.task-definition }} container-name: openems-deployment-container-edge image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_6 }}:latest diff --git a/.github/workflows/destroy-pipeline.yml b/.github/workflows/destroy-pipeline.yml index 1bc6766..e739542 100644 --- a/.github/workflows/destroy-pipeline.yml +++ b/.github/workflows/destroy-pipeline.yml @@ -13,7 +13,7 @@ env: IMAGE_NAME_VALUE_2: openems-backend # IMAGE_NAME_VALUE_3: openems-db IMAGE_NAME_VALUE_4: odoo - IMAGE_NAME_VALUE_5: odoo-db + # IMAGE_NAME_VALUE_5: odoo-db IMAGE_NAME_VALUE_6: openems-edge TERRAFORM_ACTION: destroy ECS_TD: .github/workflows/openems-deployment-td.json @@ -34,8 +34,8 @@ jobs: aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} - # Build AWS infrastructure - deploy_aws_infrastructure: + # Destroy AWS infrastructure + destroy_aws_infrastructure: name: Build AWS infrastructure needs: configure_aws_credentials runs-on: ubuntu-latest @@ -55,193 +55,3 @@ jobs: - name: Run Terraform apply/destroy working-directory: ./iac run: terraform ${{ env.TERRAFORM_ACTION }} -auto-approve - - # Create ECR repository - create_ecr_repository: - name: Create ECR repository - needs: - - deploy_aws_infrastructure - - configure_aws_credentials - if: needs.deploy_aws_infrastructure.output.terraform_action != 'destroy' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Check if ECR repositories exist - env: - # List your repository names here - REPO_NAMES: '${{ env.IMAGE_NAME_VALUE_1 }},${{ env.IMAGE_NAME_VALUE_2 }},${{ env.IMAGE_NAME_VALUE_4 }},${{ env.IMAGE_NAME_VALUE_5 }},${{ env.IMAGE_NAME_VALUE_6 }}' - run: | - IFS=',' read -ra REPOS <<< "$REPO_NAMES" - for repo in "${REPOS[@]}"; do - echo "Checking repository: $repo" - result=$(aws ecr describe-repositories --repository-names "$repo" 2>/dev/null | jq -r '.repositories[0]?.repositoryName // empty') - if [[ -z "$result" ]]; then - echo "Repository $repo does not exist." - else - echo "Repository $repo exists." - echo "repo_name_$repo=$result" >> $GITHUB_ENV - fi - done - continue-on-error: true - - - name: Create ECR repositories - env: - # List your repository names here - REPO_NAMES: '${{ env.IMAGE_NAME_VALUE_1 }},${{ env.IMAGE_NAME_VALUE_2 }},${{ env.IMAGE_NAME_VALUE_4 }},${{ env.IMAGE_NAME_VALUE_5 }},${{ env.IMAGE_NAME_VALUE_6 }}' - run: | - IFS=',' read -ra REPOS <<< "$REPO_NAMES" - for repo in "${REPOS[@]}"; do - echo "Creating repository: $repo" - if ! aws ecr describe-repositories --repository-names "$repo" 2>/dev/null; then - aws ecr create-repository --repository-name "$repo" - echo "Repository $repo created." - else - echo "Repository $repo already exists." - fi - done - # aws ecr put-registry-scanning-configuration \ - # --scan-type ENHANCED \ - # --rules '["scanFrequency" : "SCAN_ON_PUSH"]' \ - # --region ${{ env.AWS_REGION }} - - # Build Docker Image - - build_scan_push: - name: Build, Scan and push Docker image to ECR - needs: - - configure_aws_credentials - - deploy_aws_infrastructure - - create_ecr_repository - if: needs.deploy_aws_infrastructure.output.terraform_action != 'destroy' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Login to Amazon ECR - uses: aws-actions/amazon-ecr-login@v1 - - - name: Build an image from Docker Compose - run: | - docker-compose build - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_1 }}:latest' - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_2 }}:latest' - format: 'sarif' - output: 'trivy-results.sarif' - - # - name: Run Trivy vulnerability scanner - # uses: aquasecurity/trivy-action@master - # with: - # image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_3 }}:latest' - # format: 'sarif' - # output: 'trivy-results.sarif' - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_4 }}:latest' - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_5 }}:latest' - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: 'docker.io/library/${{ env.IMAGE_NAME_VALUE_6 }}:latest' - format: 'sarif' - output: 'trivy-results.sarif' - - # - name: Upload Trivy scan results to GitHub Security tab - # uses: github/codeql-action/upload-sarif@v2 - # with: - # sarif_file: 'trivy-results.sarif' - - - name: Grant execute permission to the script - run: chmod +x ./push-to-ecr.sh - - - name: Retag Docker image and Push Docker Image to Amazon ECR - run: ./push-to-ecr.sh - - create_td_revision_restart_ecs: - name: Create new task definition revision and Restart ECS - needs: - - configure_aws_credentials - - deploy_aws_infrastructure - - create_ecr_repository - - build_scan_push - if: needs.deploy_aws_infrastructure.output.terraform_action != 'destroy' - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Render Amazon ECS task definition for first container - id: render-ui-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ env.ECS_TD }} - container-name: openems-deployment-container-ui - image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_1 }}:latest - - - name: Modify Amazon ECS task definition with first container - id: render-backend-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ steps.render-ui-container.outputs.task-definition }} - container-name: openems-deployment-container-backend - image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_2 }}:latest - - # - name: Modify Amazon ECS task definition with second container - # id: render-backend-db-container - # uses: aws-actions/amazon-ecs-render-task-definition@v1 - # with: - # task-definition: ${{ steps.render-backend-container.outputs.task-definition }} - - - name: Modify Amazon ECS task definition with third container - id: render-odoo-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ steps.render-backend-container.outputs.task-definition }} - container-name: openems-deployment-container-odoo - image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_4 }}:latest - - - name: Modify Amazon ECS task definition with forth container - id: render-odoo-db-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ steps.render-odoo-container.outputs.task-definition }} - container-name: openems-deployment-container-odoo-db - image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_4 }}:latest - - - name: Modify Amazon ECS task definition with fifth container - id: render-edge-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ steps.render-odoo-db-container.outputs.task-definition }} - container-name: openems-deployment-container-edge - image: ${{ secrets.ECR_REGISTRY }}/${{ env.IMAGE_NAME_VALUE_6 }}:latest - - - name: Deploy to Amazon ECS service - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 - with: - task-definition: ${{ steps.render-backend-container.outputs.task-definition }} - service: ${{ env.ECS_SERVICE }} - cluster: ${{ env.ECS_CLUSTER }} diff --git a/.github/workflows/openems-deployment-td.json b/.github/workflows/openems-deployment-td.json index e0c5975..0f2f7b4 100644 --- a/.github/workflows/openems-deployment-td.json +++ b/.github/workflows/openems-deployment-td.json @@ -38,28 +38,6 @@ } ], "essential": false, - "environment": [ - { - "name": "DB_NAME", - "value": "openemsdb" - }, - { - "name": "DB_HOST", - "value": "applicationdb.clkigksc2ezs.us-east-1.rds.amazonaws.com" - }, - { - "name": "DB_PORT", - "value": "5432" - }, - { - "name": "DB_USER", - "value": "openems" - }, - { - "name": "DB_PASSWORD", - "value": "openempassword" - } - ], "mountPoints": [], "volumesFrom": [], "logConfiguration": { @@ -83,57 +61,23 @@ "protocol": "tcp" } ], - "essential": false, + "essential": true, "environment": [ - { - "name": "USER", - "value": "odoo" - }, - { - "name": "PASSWORD", - "value": "odoo16@2022" - }, { "name": "HOST", - "value": "db" - } - ], - "mountPoints": [], - "volumesFrom": [], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": "/ecs/openems-deployment-tds", - "awslogs-region": "us-east-1", - "awslogs-stream-prefix": "ecs" - } - }, - "systemControls": [] - }, - { - "name": "openems-deployment-container-odoo-db", - "image": "470298448112.dkr.ecr.us-east-1.amazonaws.com/odoo-db:latest", - "cpu": 0, - "portMappings": [ - { - "containerPort": 5433, - "hostPort": 5433, - "protocol": "tcp" - } - ], - "essential": false, - "environment": [ + "value": "odoodb.clkigksc2ezs.us-east-1.rds.amazonaws.com" + }, { - "name": "POSTGRES_USER", + "name": "USER", "value": "odoo" }, { - "name": "POSTGRES_PASSWORD", - "value": "odoo16@2022 " + "name": "PORT", + "value": "5432" }, { - "name": "POSTGRES_DB", - "value": "postgres" + "name": "PASSWORD", + "value": "Icui4cyou" } ], "mountPoints": [], diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 29fab47..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,14 +0,0 @@ -# Security Policy - -See https://github.com/energy-iot/docs/security/policy - -## Supported Versions - -All versions of Energy IoT Open source strive to keep up of critical and high vulnarabilities patches when available. - -https://github.com/OpenEMS/openems/releases/tag/2024.2.0 -https://github.com/OpenEMS/openems/releases/tag/2023.11.0 - -## Reporting a Vulnerability - -See https://github.com/energy-iot/docs/security/policy diff --git a/] b/] deleted file mode 100644 index 5c11fac..0000000 --- a/] +++ /dev/null @@ -1,13 +0,0 @@ - - -Signed-off-by: belloafeez - -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. -# -# On branch axmsoftware-new -# Changes to be committed: -# modified: .github/workflows/deploy-pipeline.yml - - -destroy pipeline diff --git a/addons/openems/.gitignore b/addons/openems/.gitignore new file mode 100644 index 0000000..e41d5e2 --- /dev/null +++ b/addons/openems/.gitignore @@ -0,0 +1,3 @@ +.swp +.swo +**/__pycache__ diff --git a/addons/openems/__init__.py b/addons/openems/__init__.py new file mode 100644 index 0000000..72d3ea6 --- /dev/null +++ b/addons/openems/__init__.py @@ -0,0 +1 @@ +from . import controllers, models diff --git a/addons/openems/__manifest__.py b/addons/openems/__manifest__.py new file mode 100644 index 0000000..232ebf1 --- /dev/null +++ b/addons/openems/__manifest__.py @@ -0,0 +1,40 @@ +{ + "name": "OpenEMS", + "summary": "Everything related to OpenEMS (Open Energy Management System)", + "version": "16.0.1.0.1", + "author": "OpenEMS Association e.V.", + "maintainer": "OpenEMS Association e.V.", + "contributors": [ + "Stefan Feilmeier " + "Maximilian Lang " + ], + "website": "https://openems.io", + "license": "AGPL-3", + "category": "Specific Industry Applications", + "depends": ["base", "web", "mail", "crm", "resource", "stock", "web_m2x_options", "partner_firstname"], + "data": [ + "data/ir_config_parameter.xml", + "data/res_partner_category.xml", + "security/openems.xml", + "security/ir.model.access.csv", + "report/setup_protocol.xml", + "views/device.xml", + "views/partner.xml", + "views/setup_protocol.xml", + "views/user.xml", + "views/stock_production_lot_views.xml", + "mail/openems/alerting_offline.xml", + "mail/openems/alerting_sum_state.xml", + "mail/openems/setup_protocol_customer.xml", + "mail/openems/setup_protocol_installer.xml", + "mail/openems/user_registration.xml", + ], + "demo": ["data/demo.xml"], + "js": [], + "css": [], + "qweb": [], + "images": [], + "test": [], + "installable": True, + "application": True, +} diff --git a/addons/openems/controllers/__init__.py b/addons/openems/controllers/__init__.py new file mode 100644 index 0000000..3e863cd --- /dev/null +++ b/addons/openems/controllers/__init__.py @@ -0,0 +1 @@ +from . import openems_backend, setup_protocol, user, alerting diff --git a/addons/openems/controllers/alerting.py b/addons/openems/controllers/alerting.py new file mode 100644 index 0000000..7a848f2 --- /dev/null +++ b/addons/openems/controllers/alerting.py @@ -0,0 +1,86 @@ +import logging +from datetime import datetime +from enum import Enum + +from odoo import http +from odoo.http import request + +class SumState(Enum): + FAULT = 0 + WARNING = 1 + +class Message: + sentAt: datetime + edgeId: str + userIds: list[int] + + def __init__(self, sentAt: datetime, edgeId: str, userIds: list[int]) -> None: + self.sentAt = sentAt + self.edgeId = edgeId + self.userIds = userIds + +class SumStateMessage(Message): + state: SumState + + def __init__(self, sentAt: datetime, edgeId: str, userIds: list[int], state: SumState) -> None: + super().__init__(sentAt, edgeId, userIds) + self.state = state + +class Alerting(http.Controller): + __logger = logging.getLogger("Alerting") + + @http.route("/openems_backend/mail/alerting_sum_state", type="json", auth="user") + def sum_state_alerting(self, sentAt: str, params: list[dict]): + msgs = self.__get_sum_state_params(sentAt, params) + update_func = lambda role, at: { role.write({"sum_state_last_notification": at})} + + if len(msgs) == 0: + self.__logger.error("Scheduled SumState-Alerting-Mail without any recipients!!!") + + template = request.env.ref('openems.alerting_sum_state') + for msg in msgs: + self.__send_mails(template, msg, update_func) + + return {} + + @http.route("/openems_backend/mail/alerting_offline", type="json", auth="user") + def offline_alerting(self, sentAt: str, params: list[dict]): + msgs = self.__get_offline_params(sentAt, params) + update_func = lambda role, at: { role.write({"offline_last_notification": at})} + + template = request.env.ref("openems.alerting_offline") + for msg in msgs: + self.__send_mails(template, msg, update_func) + + return {} + + def __get_offline_params(self, sentAt, params) -> list[Message]: + msgs = list() + sent = datetime.strptime(sentAt, "%Y-%m-%d %H:%M:%S") + for param in params: + edgeId = param["edgeId"] + recipients = param["recipients"] + msgs.append(Message(sent, edgeId, recipients)); + return msgs + + def __get_sum_state_params(self, sentAt, params) -> list[SumStateMessage]: + msgs = list() + sent = datetime.strptime(sentAt, "%Y-%m-%d %H:%M:%S") + for param in params: + edgeId = param["edgeId"] + recipients = param["recipients"] + state = param["state"] + msgs.append(SumStateMessage(sent, edgeId, recipients, state)); + return msgs + + def __send_mails(self, template, msg: Message, update_func): + roles = http.request.env['openems.alerting'].search( + [('user_id','in',msg.userIds),('device_id','=',msg.edgeId)] + ) + + for role in roles: + try: + template.send_mail(res_id=role.id, force_send=True) + update_func(role, msg.sentAt) + except Exception as err: + self.__logger.error("[" + str(err) + "] Unable to send template[" + str(template.name) +"] to edgeUser[user=" + str(role.id) + ", edge=" + str(msg.edgeId)+ "]") \ No newline at end of file diff --git a/addons/openems/controllers/const.py b/addons/openems/controllers/const.py new file mode 100644 index 0000000..c6fbc01 --- /dev/null +++ b/addons/openems/controllers/const.py @@ -0,0 +1,5 @@ +from odoo.modules.module import get_module_resource + +import base64 + +OPENEMS_LOGO_BASE64 = base64.b64encode(open(get_module_resource('openems', 'data', 'OpenEMS-Logo.jpg') , "rb").read()) \ No newline at end of file diff --git a/addons/openems/controllers/openems_backend.py b/addons/openems/controllers/openems_backend.py new file mode 100644 index 0000000..d0dcb80 --- /dev/null +++ b/addons/openems/controllers/openems_backend.py @@ -0,0 +1,276 @@ +from odoo import http + + +class OpenemsBackend(http.Controller): + @http.route("/openems_backend/info", auth="user", type="json") + def index(self): + # Get user + user_id = http.request.env.context.get("uid") + res_users = http.request.env["res.users"].sudo() + user_rec = res_users.search_read( + [("id", "=", user_id)], + ["login", "name", "groups_id", "global_role", "openems_language"], + )[0] + res_users.browse([user_id]) + + # Get res group model + res_groups_model = http.request.env["res.groups"].sudo() + + # Get Manager and Reader group + manager_group = res_groups_model.env.ref("openems.group_openems_manager") + reader_group = res_groups_model.env.ref("openems.group_openems_reader") + + manager_group_id = manager_group["id"] + reader_group_id = reader_group["id"] + + # Get user attributes + global_role = user_rec["global_role"] + if manager_group_id in user_rec["groups_id"]: + # Manager group + global_role = "admin" + + # return empty device (use pagination) list if user is manager or reader + if manager_group_id in user_rec["groups_id"] or reader_group_id in user_rec["groups_id"]: + return { + "user": { + "id": user_rec["id"], + "login": user_rec["login"], + "name": user_rec["name"], + "global_role": global_role, + "language": user_rec["openems_language"], + "has_multiple_edges": True + }, + "devices": [], + } + + # Get specific Device roles + device_user_role_model = http.request.env["openems.device_user_role"] + user_role_ids = device_user_role_model.search_read( + [("user_id", "=", user_id)], ["id", "role"] + ) + + # Get Devices + device_model = http.request.env["openems.device"] + devices = device_model.search_read( + [], ["id", "name", "user_role_ids", "comment", "producttype", + "lastmessage", "first_setup_protocol_date", "openems_sum_state_level"] + ) + + devs = [] + for device_rec in devices: + # Set user role per group + role = "guest" + if manager_group_id in user_rec["groups_id"]: + # Manager group + role = "admin" + elif reader_group_id in user_rec["groups_id"]: + # Reader group + role = "guest" + + # Set specific user role + for device_role_id in device_rec["user_role_ids"]: + for user_role_id in user_role_ids: + if device_role_id == user_role_id["id"]: + role = user_role_id["role"] + + # Prepare result + dev = { + "id": device_rec["id"], + "name": device_rec["name"], + "comment": device_rec["comment"], + "producttype": device_rec["producttype"], + "role": role, + "lastmessage": device_rec["lastmessage"], + "openems_sum_state_level": device_rec["openems_sum_state_level"] + } + + if device_rec["first_setup_protocol_date"]: + dev["first_setup_protocol_date"] = device_rec[ + "first_setup_protocol_date" + ] + + devs.append(dev) + + return { + "user": { + "id": user_rec["id"], + "login": user_rec["login"], + "name": user_rec["name"], + "global_role": global_role, + "language": user_rec["openems_language"], + "has_multiple_edges": len(devs) > 1 + }, + "devices": devs, + } + + @http.route("/openems_backend/get_edge_with_role", auth="user", type="json") + def get_edge_with_role(self, edge_id: str): + user_id = http.request.env.context.get("uid") + res_users = http.request.env["res.users"].sudo() + user_rec = res_users.search_read( + [("id", "=", user_id)], + ["login", "name", "groups_id"], + )[0] + + # Get res group model + res_groups_model = http.request.env["res.groups"].sudo() + + # Get Manager and Reader group + manager_group = res_groups_model.env.ref("openems.group_openems_manager") + reader_group = res_groups_model.env.ref("openems.group_openems_reader") + + manager_group_id = manager_group["id"] + reader_group_id = reader_group["id"] + + # get devices for which the user has permissions + device_model = http.request.env["openems.device"] + devices = device_model.search_read( + [("name", "=", edge_id)], + ["id", "name", "comment", "producttype", "lastmessage", "first_setup_protocol_date", "openems_sum_state_level"]) + + if len(devices) != 1: + return {} + + device = devices[0] + + # Get specific Device roles + device_user_role_model = http.request.env["openems.device_user_role"] + device_user_roles = device_user_role_model.search_read( + [("user_id", "=", user_id), + ("device_id", "=", device["id"])], ["id", "role"] + ) + + # Set user role per group + role = "guest" + if manager_group_id in user_rec["groups_id"]: + # Manager group + role = "admin" + elif reader_group_id in user_rec["groups_id"]: + # Reader group + role = "guest" + + # Set specific user role + if len(device_user_roles) > 0: + role = device_user_roles[0]["role"] + + dev = { + "id": device["id"], + "name": device["name"], + "comment": device["comment"], + "producttype": device["producttype"], + "role": role, + "lastmessage": device["lastmessage"], + "openems_sum_state_level": device["openems_sum_state_level"] + } + if device["first_setup_protocol_date"]: + dev["first_setup_protocol_date"] = device["first_setup_protocol_date"] + + return dev + + @http.route("/openems_backend/get_edges", auth="user", type="json") + def get_edges(self, limit, page, query=None, searchParams=None): + # Get user + user_id = http.request.env.context.get("uid") + res_users = http.request.env["res.users"].sudo() + user_rec = res_users.search_read( + [("id", "=", user_id)], + ["login", "name", "groups_id", "global_role"], + )[0] + + # Get res group model + res_groups_model = http.request.env["res.groups"].sudo() + + # Get Manager and Reader group + manager_group = res_groups_model.env.ref("openems.group_openems_manager") + reader_group = res_groups_model.env.ref("openems.group_openems_reader") + + manager_group_id = manager_group["id"] + reader_group_id = reader_group["id"] + + # Get specific Device roles + device_user_role_model = http.request.env["openems.device_user_role"] + user_role_ids = device_user_role_model.search_read( + [("user_id", "=", user_id)], ["id", "role"] + ) + + domains = [] + logical_operators = [] + additional_domains = [] + if query: + logical_operators.extend(['|', '|']) + domains = [ + ("name", "ilike", query), + ("comment", "ilike", query), + ("producttype", "ilike", query)] + + if searchParams: + if searchParams.get("producttype"): + additional_domains.append( + ("producttype", "in", searchParams.get("producttype"))) + + if searchParams.get("sumState"): + sum_states = list(map(lambda s: s.lower(), searchParams.get("sumState"))) + additional_domains.append( + ("openems_sum_state_level", "in", sum_states)) + + if "isOnline" in searchParams: + additional_domains.append( + ("openems_is_connected", "=", searchParams.get("isOnline"))) + + if len(additional_domains) > 1: + for _ in range(len(additional_domains) - 1): + logical_operators.insert(0, '&') + + # insert 'and' if both are not 'None' + if query and searchParams: + logical_operators.insert(0, '&') + + domains.extend(additional_domains) + logical_operators.extend(domains) + + # Get Devices + device_model = http.request.env["openems.device"] + devices = device_model.search_read( + logical_operators, + ["id", "name", "user_role_ids", "comment", "producttype", + "lastmessage", "first_setup_protocol_date", "openems_sum_state_level"], + limit=limit, offset=(page * limit) + ) + devs = [] + for device_rec in devices: + # Set user role per group + role = "guest" + if manager_group_id in user_rec["groups_id"]: + # Manager group + role = "admin" + elif reader_group_id in user_rec["groups_id"]: + # Reader group + role = "guest" + + # Set specific user role + for device_role_id in device_rec["user_role_ids"]: + for user_role_id in user_role_ids: + if device_role_id == user_role_id["id"]: + role = user_role_id["role"] + + # Prepare result + dev = { + "id": device_rec["id"], + "name": device_rec["name"], + "comment": device_rec["comment"], + "producttype": device_rec["producttype"], + "role": role, + "lastmessage": device_rec["lastmessage"], + "openems_sum_state_level": device_rec["openems_sum_state_level"] + } + + if device_rec["first_setup_protocol_date"]: + dev["first_setup_protocol_date"] = device_rec[ + "first_setup_protocol_date" + ] + + devs.append(dev) + + return { + "devices": devs, + } diff --git a/addons/openems/controllers/setup_protocol.py b/addons/openems/controllers/setup_protocol.py new file mode 100644 index 0000000..d64b18e --- /dev/null +++ b/addons/openems/controllers/setup_protocol.py @@ -0,0 +1,142 @@ +import base64 + +from odoo import http +from odoo.http import request + + +class SetupProtocol(http.Controller): + @http.route("/openems_backend/sendSetupProtocolEmail", type="json", auth="user") + def index(self, setupProtocolId, edgeId): + setup_protocol_model = request.env["openems.setup_protocol"] + setup_protocol_record = setup_protocol_model.search_read( + [("id", "=", setupProtocolId)] + ) + if len(setup_protocol_record) != 1: + raise ValueError("Setup protocol not found for id [" + edgeId + "]") + + device_model = request.env["openems.device"] + device_rec = device_model.search_read([("name", "=", edgeId)]) + if len(device_rec) != 1: + raise ValueError("Device not found for id [" + edgeId + "]") + + name = ( + "IBN-" + + edgeId + + "-" + + setup_protocol_record[0]["create_date"].strftime("%d.%m.%Y") + + ".pdf" + ) + + data = request.env.ref( + "openems.action_openems_setup_protocol_report" + )._render_qweb_pdf([setupProtocolId]) + ibnPdf = request.env["ir.attachment"].create( + { + "res_model": "openems.device", + "res_id": device_rec[0]["id"], + "name": name, + "store_fname": name, + "datas": base64.b64encode(data[0]), + } + ) + + templates = self.getTemplates(device_rec[0]['oem'], ibnPdf) + + templates['installer'].send_mail(setupProtocolId, force_send=True) + templates['customer'].send_mail(setupProtocolId, force_send=True) + + return {} + + def getTemplates(self, oem: str, protocol): + templates = {'customer': None, 'installer': None} + + templates['customer'] = request.env.ref( + "openems.setup_protocol_email_customer") + templates['installer'] = request.env.ref( + "openems.setup_protocol_email_installer") + + logo = request.env.ref("openems.attachment_logo_openems") + + templates['customer'].attachment_ids = [ + (6, 0, [protocol.id, logo.id])] + templates['installer'].attachment_ids = [ + (6, 0, [protocol.id, logo.id])] + + return templates + + @http.route('/openems_backend/get_latest_setup_protocol', type='json', auth='user') + def get_latest_setup_protocol(self, edge_name): + # search for device + device_model = request.env['openems.device'] + device = device_model.search([('name', '=', edge_name)]) + + response = dict() + if not len(device.setup_protocol_ids) > 0: + return response + + latest_protocol = device.setup_protocol_ids[0] + + # build customer object + customer = latest_protocol.customer_id + customer_values = dict({ + "firstname": customer['firstname'], + "lastname": customer['lastname'], + "email": customer['email'], + "phone": customer['phone'], + "address": { + "street": customer['street'], + "city": customer['city'], + "zip": customer['zip'], + "country": customer['country_id']['name'] + } + }) + + # check company for customer + customer_company = customer['commercial_company_name'] + if customer_company: + customer_values.update({ + "company": { + "name": customer['commercial_company_name'] + } + }) + response.update({"customer": customer_values}) + + # check different location is available + location = latest_protocol['different_location_id'] + if location: + location_values = dict({ + "firstname": location['firstname'], + "lastname": location['lastname'], + "email": location['email'], + "phone": location['phone'], + "address": { + "street": location['street'], + "city": location['city'], + "zip": location['zip'], + "country": location['country_id']['name'] + } + }) + + # check company for different location + different_location_company = location['commercial_company_name'] + if different_location_company: + location_values.update({ + "company": { + "name": location['commercial_company_name'] + } + }) + response.update({"location": location_values}) + + # build items object + items = list() + for item in latest_protocol.item_ids: + items.append({ + "view": item['view'], + "field": item['field'], + "category": item['category'], + "name": item['name'], + "value": item['value'] + }) + response.update({"items": items}) + + return response diff --git a/addons/openems/controllers/user.py b/addons/openems/controllers/user.py new file mode 100644 index 0000000..75ec137 --- /dev/null +++ b/addons/openems/controllers/user.py @@ -0,0 +1,32 @@ +from odoo import http +from odoo.http import request + +class User(http.Controller): + @http.route("/openems_backend/sendRegistrationEmail", type="json", auth="user") + def index(self, userId, password=None, oem: str = ''): + user_model = request.env["res.users"] + user_record = user_model.search_read([("id", "=", userId)], ["partner_id"]) + if len(user_record) != 1: + raise ValueError("User not found for id [" + userId + "]") + + partner = user_record[0] + partner_id = partner.get("partner_id") + if partner_id is None: + raise ValueError("User has no partner") + + if password is None: + password = "*****" + # load template + template = self.getTemplate(oem) + # set mail values + email_values = { + 'password': password + } + # send mail + template.with_context(email_values).send_mail( + res_id=partner_id[0], force_send=True) + return {} + + def getTemplate(self, oem: str): + template = request.env.ref("openems.registration_email") + return template diff --git a/addons/openems/data/OpenEMS-Logo.jpg b/addons/openems/data/OpenEMS-Logo.jpg new file mode 100644 index 0000000..13ae0fb Binary files /dev/null and b/addons/openems/data/OpenEMS-Logo.jpg differ diff --git a/addons/openems/data/demo.xml b/addons/openems/data/demo.xml new file mode 100644 index 0000000..f4bf18e --- /dev/null +++ b/addons/openems/data/demo.xml @@ -0,0 +1,12 @@ + + + + + + edge0 + OpenEMS Edge #0 + DEMO_API_KEY + + + + diff --git a/addons/openems/data/ir_config_parameter.xml b/addons/openems/data/ir_config_parameter.xml new file mode 100644 index 0000000..b1da5cf --- /dev/null +++ b/addons/openems/data/ir_config_parameter.xml @@ -0,0 +1,9 @@ + + + + + edge_monitoring_url + http://localhost:8082/device/ + + + diff --git a/addons/openems/data/res_partner_category.xml b/addons/openems/data/res_partner_category.xml new file mode 100644 index 0000000..f56c0e0 --- /dev/null +++ b/addons/openems/data/res_partner_category.xml @@ -0,0 +1,11 @@ + + + + + Created via IBN + + + Customer + + + diff --git a/addons/openems/i18n/de.po b/addons/openems/i18n/de.po new file mode 100644 index 0000000..cc4c660 --- /dev/null +++ b/addons/openems/i18n/de.po @@ -0,0 +1,1666 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * openems +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0-20240218\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-02-18 11:57+0000\n" +"PO-Revision-Date: 2024-02-18 11:57+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: openems +#: model:mail.template,body_html:openems.setup_protocol_email_installer +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" OpenEMS setup protocol \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Dear \n" +" ,\n" +"
\n" +"
\n" +"
please find the setup protocol for your customer attached.\n" +"
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:mail.template,body_html:openems.registration_email +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" Registrierung erfolgreich \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Guten Tag \n" +" ,\n" +"
\n" +"
\n" +"
Ihr Zugang zu OpenEMS UI wurde erstellt.
\n" +"
\n" +"
Sie können sich auf OpenEMS UI einloggen, um auf das Online-Monitoring zuzugreifen.
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
Ihre Zugangsdaten:
E-Mail\n" +" \n" +"
Passwort\n" +" \n" +"
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:mail.template,body_html:openems.setup_protocol_email_customer +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" Your OpenEMS Edge setup protocol \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Welcome to OpenEMS,
\n" +"
\n" +"
please find your setup protocol for OpenEMS Edge attached.
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:mail.template,body_html:openems.alerting_email_generic +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" OpenEMS Alert - Edge is offline\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Dear \n" +" ,\n" +"
\n" +"
\n" +"
Your OpenEMS Edge with number is offline since:
\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" (UTC)\n" +" \n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
Info
\n" +" Edge-Version\n" +" \n" +" \n" +"
\n" +" Type\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
Wenn Sie keine E-Mail Benachrichtigung mehr wünschen, können Sie die Funktion hier deaktivieren.
\n" +"
\n" +"
Dies ist eine automatisch generierte Nachricht. Bitte antworten Sie nicht auf diese Nachricht!
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:ir.actions.report,print_report_name:openems.action_openems_setup_protocol_report +msgid "" +"('IBN-' + object.openems_device_id.name + '-' + " +"object.create_date.strftime('%d.%m.%Y'))" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__apikey +msgid "API-Key" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Access Role in Online-Monitoring" +msgstr "Zugriffsrollen im Online-Monitoring" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__active +msgid "Active" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__admin +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__admin +msgid "Admin" +msgstr "" + +#. module: openems +#: model:ir.ui.menu,name:openems.menu_openems_admin +msgid "Administration" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +msgid "Archived" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_users__branding_partner_id +msgid "Branding Partner" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__category +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__category +msgid "Category" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__comment +msgid "Comment" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Configuration" +msgstr "Konfiguration" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Configuration Updates" +msgstr "Konfigurationsänderungen" + +#. module: openems +#: model:ir.model,name:openems.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__create_uid +msgid "Created by" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__create_date +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__create_date +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__create_date +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__create_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__create_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__create_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__create_date +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__create_date +msgid "Created on" +msgstr "" + +#. module: openems +#: model:res.partner.category,name:openems.res_partner_category_created_via_ibn +msgid "Created via IBN" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__timestamp +msgid "Creation date" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__customer_id +#: model:res.partner.category,name:openems.res_partner_category_customer +msgid "Customer" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__cz +msgid "Czech" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Datum:" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "Description" +msgstr "Bezeichnung" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_openemsconfigupdate_tree +msgid "Details" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_stock_lot__device_id +#: model:ir.model.fields,field_description:openems.field_stock_lot__device_ids +msgid "Device" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_openemsconfigupdate +#: model:ir.ui.menu,name:openems.menu_openems_admin_openemsconfigupdate +msgid "Device Configuration Updates" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_device +#: model:ir.ui.menu,name:openems.menu_openems_content_devices +msgid "Devices" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__different_location_id +msgid "Different Location" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__display_name +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__display_name +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__display_name +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__display_name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__display_name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__display_name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__display_name +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__display_name +msgid "Display Name" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__nl +msgid "Dutch" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.alerting_email_generic +msgid "E-Mail Alerting" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.registration_email +msgid "E-Mail Kunden Registrierung" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.setup_protocol_email_customer +msgid "E-Mail setup protocol for customer" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.setup_protocol_email_installer +msgid "E-Mail setup protocol for installer" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "E-Mail-Adresse" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__emshardware +msgid "EMS Hardware" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__en +msgid "English" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__item_ids +msgid "Entry Items" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__fault +msgid "Fault" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__field +msgid "Field Identifier" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Firmenname" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__first_setup_protocol_date +msgid "First Setup Protocol Date" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__fr +msgid "French" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "General" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__de +msgid "German" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_users__global_role +msgid "Global Role" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__guest +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__guest +msgid "Guest" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Hardware" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__has_message +msgid "Has Message" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__hu +msgid "Hungarian" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__id +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__id +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__id +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__id +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__id +msgid "ID" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_has_error +#: model:ir.model.fields,help:openems.field_openems_device__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__info +msgid "Info" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Installateur" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Installation" +msgstr "Inbetriebnahme" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Installation Log" +msgstr "Inbetriebnahme Protokolle" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__setup_password +msgid "Installation key" +msgstr "Installateursschlüssel" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_partner__installer_setup_protocols_ids +#: model:ir.model.fields,field_description:openems.field_res_users__installer_setup_protocols_ids +msgid "Installed OpenEMS Edge" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__installer_id +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__installer +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__installer +msgid "Installer" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__internalnote +msgid "Internal note" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Is connected" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Kontaktdaten Endkunde" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Land" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device____last_update +#: model:ir.model.fields,field_description:openems.field_openems_device_tag____last_update +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role____last_update +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate____last_update +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol____last_update +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item____last_update +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot____last_update +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage____last_update +msgid "Last Modified on" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__write_date +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__write_date +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__write_date +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__write_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__write_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__write_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__write_date +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__write_date +msgid "Last Updated on" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__lastupdate +msgid "Last data update" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__lastmessage +msgid "Last message" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__last_notification +msgid "Last notification sent" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_stock_lot +msgid "Lot/Serial" +msgstr "Los/Serie" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: openems +#: model:res.groups,name:openems.group_openems_manager +msgid "Manager" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__manual_setup_date +msgid "Manual Setup Date" +msgstr "" + +#. module: openems +#: model:res.groups,comment:openems.group_openems_manager +msgid "Members of this group can manage all devices" +msgstr "" + +#. module: openems +#: model:res.groups,comment:openems.group_openems_reader +msgid "Members of this group have reading access to all devices" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__text +msgid "Message" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_ids +msgid "Messages" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__name +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__name +msgid "Name" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Name Installateur" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Name Kontaktperson" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__name_number +msgid "Name Number" +msgstr "" + +#. module: openems +#: model:ir.model.constraint,message:openems.constraint_openems_device_unique_name +msgid "Name needs to be unique" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__time_to_wait +msgid "Notification" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OE Connected" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OE State" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OE Version" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__oem +msgid "OEM Branding" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__ok +msgid "Ok" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__monitoring_url +msgid "Online-Monitoring" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__oem__openems +#: model:ir.module.category,name:openems.module_category_openems +#: model:ir.ui.menu,name:openems.menu_openems +#: model_terms:ir.ui.view,arch_db:openems.openems_users_form +msgid "OpenEMS" +msgstr "" + +#. module: openems +#: model:mail.template,subject:openems.alerting_email_generic +msgid "OpenEMS Alert - Edge is offline" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "OpenEMS Association e.V." +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_config_components +msgid "OpenEMS Config" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_config +msgid "OpenEMS Config Full" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_config_update_ids +msgid "OpenEMS Config Updates" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_openemsconfigupdate_tree +msgid "OpenEMS Configuration Updates" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__device_id +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__device_id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__device_id +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__device_id +#: model:ir.model.fields.selection,name:openems.selection__openems_device__producttype__openems-edge +#: model:ir.ui.menu,name:openems.menu_openems_content +msgid "OpenEMS Edge" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_device +msgid "OpenEMS Edge Device" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_openemsconfigupdate +msgid "OpenEMS Edge Device Configuration Update" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_device_tag +msgid "OpenEMS Edge Device Tag" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_device_user_role +msgid "OpenEMS Edge Device User Role" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_setup_protocol_item +msgid "OpenEMS Edge Setup Protocol Entry Item" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_setup_protocol_production_lot +msgid "OpenEMS Edge Setup Protocol Serial Number" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_setup_protocol +msgid "OpenEMS Edge Setup Protocols (IBN)" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_systemmessage +msgid "OpenEMS Edge Systemmessage" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_is_connected +msgid "OpenEMS Is connected" +msgstr "" + +#. module: openems +#: model:ir.actions.report,name:openems.action_openems_setup_protocol_report +msgid "OpenEMS Setup Protocol" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_sum_state_level +msgid "OpenEMS State" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_version +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +msgid "OpenEMS Version" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OpenEMS-Number" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_users__openems_language +msgid "Openems Language" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Ort" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__owner +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__owner +msgid "Owner" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_partner__customer_setup_protocols_ids +#: model:ir.model.fields,field_description:openems.field_res_users__customer_setup_protocols_ids +msgid "Owner of OpenEMS Edge" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "PLZ" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__setup_password +msgid "Password for commissioning by the installer" +msgstr "Passwort für die Inbetriebnahme durch den Installateur" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_setup_protocol_form +msgid "Print" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Product" +msgstr "Produkt" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__producttype +msgid "Product type" +msgstr "" + +#. module: openems +#: model:res.groups,name:openems.group_openems_reader +msgid "Read access" +msgstr "" + +#. module: openems +#: model:mail.template,subject:openems.registration_email +msgid "Registrierung erfolgreich" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__role +msgid "Role" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__user_role_ids +#: model:ir.model.fields,field_description:openems.field_res_users__device_role_ids +msgid "Roles" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Security" +msgstr "Sicherheit" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__lot_id +msgid "Serial Number" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__productionlot_ids +msgid "Serial Numbers" +msgstr "" + +#. module: openems +#: model:ir.model.constraint,message:openems.constraint_openems_device_unique_stock_production_lot_id +msgid "Serial number needs to be unique" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__setup_protocol_id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__setup_protocol_id +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Setup Protocol" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_admin_setup_protocol +#: model:ir.model.fields,field_description:openems.field_openems_device__setup_protocol_ids +#: model:ir.ui.menu,name:openems.menu_openems_admin_setup_protocol +#: model_terms:ir.ui.view,arch_db:openems.res_partner_form +msgid "Setup Protocols" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__sequence +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__sequence +msgid "Sort" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__es +msgid "Spanish" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Speicherstandort" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Status" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__stock_production_lot_id +msgid "Stock Production Lot" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Straße / Hausnummer" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "System Messages" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_systemmessage +#: model:ir.model.fields,field_description:openems.field_openems_device__systemmessage_ids +#: model:ir.ui.menu,name:openems.menu_openems_admin_systemmessages +msgid "Systemmessages" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Systemstatus" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__tag_ids +msgid "Tags" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Telefonnummer" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__text_teaser +msgid "Text Teaser" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__details +msgid "Update Details" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__teaser +msgid "Update Details Teaser" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_res_users +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__user_id +msgid "User" +msgstr "Benutzer" + +#. module: openems +#: model:ir.model.constraint,message:openems.constraint_openems_device_user_role_device_user_uniq +msgid "User already exists for this device." +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_systemmessage_tree +msgid "Users" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__value +msgid "Value" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__view +msgid "View Identifier" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Vor- Nachname" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__warning +msgid "Warning" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: openems +#: model:mail.template,subject:openems.setup_protocol_email_customer +#: model:mail.template,subject:openems.setup_protocol_email_installer +msgid "Your OpenEMS setup protocol for {{object.openems_device_id.name}}" +msgstr "" diff --git a/addons/openems/i18n/openems.pot b/addons/openems/i18n/openems.pot new file mode 100644 index 0000000..c02fb93 --- /dev/null +++ b/addons/openems/i18n/openems.pot @@ -0,0 +1,1666 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * openems +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0-20240218\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-02-18 11:56+0000\n" +"PO-Revision-Date: 2024-02-18 11:56+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: openems +#: model:mail.template,body_html:openems.setup_protocol_email_installer +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" OpenEMS setup protocol \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Dear \n" +" ,\n" +"
\n" +"
\n" +"
please find the setup protocol for your customer attached.\n" +"
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:mail.template,body_html:openems.registration_email +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" Registrierung erfolgreich \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Guten Tag \n" +" ,\n" +"
\n" +"
\n" +"
Ihr Zugang zu OpenEMS UI wurde erstellt.
\n" +"
\n" +"
Sie können sich auf OpenEMS UI einloggen, um auf das Online-Monitoring zuzugreifen.
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
Ihre Zugangsdaten:
E-Mail\n" +" \n" +"
Passwort\n" +" \n" +"
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:mail.template,body_html:openems.setup_protocol_email_customer +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" Your OpenEMS Edge setup protocol \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Welcome to OpenEMS,
\n" +"
\n" +"
please find your setup protocol for OpenEMS Edge attached.
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:mail.template,body_html:openems.alerting_email_generic +msgid "" +"\n" +"\n" +"\n" +"\n" +"\n" +" OpenEMS Alert - Edge is offline\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +"

\n" +" \n" +"
\n" +"
Dear \n" +" ,\n" +"
\n" +"
\n" +"
Your OpenEMS Edge with number is offline since:
\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" (UTC)\n" +" \n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
Info
\n" +" Edge-Version\n" +" \n" +" \n" +"
\n" +" Type\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
Best Regards
\n" +"
\n" +"
OpenEMS Association e.V.
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
Wenn Sie keine E-Mail Benachrichtigung mehr wünschen, können Sie die Funktion hier deaktivieren.
\n" +"
\n" +"
Dies ist eine automatisch generierte Nachricht. Bitte antworten Sie nicht auf diese Nachricht!
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"\n" +"\n" +"\n" +" \n" +" " +msgstr "" + +#. module: openems +#: model:ir.actions.report,print_report_name:openems.action_openems_setup_protocol_report +msgid "" +"('IBN-' + object.openems_device_id.name + '-' + " +"object.create_date.strftime('%d.%m.%Y'))" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__apikey +msgid "API-Key" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Access Role in Online-Monitoring" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__active +msgid "Active" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__admin +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__admin +msgid "Admin" +msgstr "" + +#. module: openems +#: model:ir.ui.menu,name:openems.menu_openems_admin +msgid "Administration" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +msgid "Archived" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_users__branding_partner_id +msgid "Branding Partner" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__category +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__category +msgid "Category" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__comment +msgid "Comment" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Configuration" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Configuration Updates" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_res_partner +msgid "Contact" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__create_uid +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__create_uid +msgid "Created by" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__create_date +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__create_date +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__create_date +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__create_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__create_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__create_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__create_date +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__create_date +msgid "Created on" +msgstr "" + +#. module: openems +#: model:res.partner.category,name:openems.res_partner_category_created_via_ibn +msgid "Created via IBN" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__timestamp +msgid "Creation date" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__customer_id +#: model:res.partner.category,name:openems.res_partner_category_customer +msgid "Customer" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__cz +msgid "Czech" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Datum:" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "Description" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_openemsconfigupdate_tree +msgid "Details" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_stock_lot__device_id +#: model:ir.model.fields,field_description:openems.field_stock_lot__device_ids +msgid "Device" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_openemsconfigupdate +#: model:ir.ui.menu,name:openems.menu_openems_admin_openemsconfigupdate +msgid "Device Configuration Updates" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_device +#: model:ir.ui.menu,name:openems.menu_openems_content_devices +msgid "Devices" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__different_location_id +msgid "Different Location" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__display_name +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__display_name +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__display_name +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__display_name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__display_name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__display_name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__display_name +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__display_name +msgid "Display Name" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__nl +msgid "Dutch" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.alerting_email_generic +msgid "E-Mail Alerting" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.registration_email +msgid "E-Mail Kunden Registrierung" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.setup_protocol_email_customer +msgid "E-Mail setup protocol for customer" +msgstr "" + +#. module: openems +#: model:mail.template,name:openems.setup_protocol_email_installer +msgid "E-Mail setup protocol for installer" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "E-Mail-Adresse" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__emshardware +msgid "EMS Hardware" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__en +msgid "English" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__item_ids +msgid "Entry Items" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__fault +msgid "Fault" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__field +msgid "Field Identifier" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Firmenname" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__first_setup_protocol_date +msgid "First Setup Protocol Date" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__fr +msgid "French" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "General" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__de +msgid "German" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_users__global_role +msgid "Global Role" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__guest +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__guest +msgid "Guest" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Hardware" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__has_message +msgid "Has Message" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__hu +msgid "Hungarian" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__id +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__id +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__id +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__id +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__id +msgid "ID" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_has_error +#: model:ir.model.fields,help:openems.field_openems_device__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__info +msgid "Info" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Installateur" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Installation" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Installation Log" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__setup_password +msgid "Installation key" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_partner__installer_setup_protocols_ids +#: model:ir.model.fields,field_description:openems.field_res_users__installer_setup_protocols_ids +msgid "Installed OpenEMS Edge" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__installer_id +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__installer +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__installer +msgid "Installer" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__internalnote +msgid "Internal note" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Is connected" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Kontaktdaten Endkunde" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Land" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device____last_update +#: model:ir.model.fields,field_description:openems.field_openems_device_tag____last_update +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role____last_update +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate____last_update +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol____last_update +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item____last_update +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot____last_update +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage____last_update +msgid "Last Modified on" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__write_uid +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__write_date +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__write_date +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__write_date +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__write_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__write_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__write_date +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__write_date +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__write_date +msgid "Last Updated on" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__lastupdate +msgid "Last data update" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__lastmessage +msgid "Last message" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__last_notification +msgid "Last notification sent" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_stock_lot +msgid "Lot/Serial" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: openems +#: model:res.groups,name:openems.group_openems_manager +msgid "Manager" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__manual_setup_date +msgid "Manual Setup Date" +msgstr "" + +#. module: openems +#: model:res.groups,comment:openems.group_openems_manager +msgid "Members of this group can manage all devices" +msgstr "" + +#. module: openems +#: model:res.groups,comment:openems.group_openems_reader +msgid "Members of this group have reading access to all devices" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__text +msgid "Message" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_ids +msgid "Messages" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__name +#: model:ir.model.fields,field_description:openems.field_openems_device_tag__name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__name +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__name +msgid "Name" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Name Installateur" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Name Kontaktperson" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__name_number +msgid "Name Number" +msgstr "" + +#. module: openems +#: model:ir.model.constraint,message:openems.constraint_openems_device_unique_name +msgid "Name needs to be unique" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__time_to_wait +msgid "Notification" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OE Connected" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OE State" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OE Version" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__oem +msgid "OEM Branding" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__ok +msgid "Ok" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__monitoring_url +msgid "Online-Monitoring" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__oem__openems +#: model:ir.module.category,name:openems.module_category_openems +#: model:ir.ui.menu,name:openems.menu_openems +#: model_terms:ir.ui.view,arch_db:openems.openems_users_form +msgid "OpenEMS" +msgstr "" + +#. module: openems +#: model:mail.template,subject:openems.alerting_email_generic +msgid "OpenEMS Alert - Edge is offline" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "OpenEMS Association e.V." +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_config_components +msgid "OpenEMS Config" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_config +msgid "OpenEMS Config Full" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_config_update_ids +msgid "OpenEMS Config Updates" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_openemsconfigupdate_tree +msgid "OpenEMS Configuration Updates" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__device_id +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__device_id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__device_id +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__device_id +#: model:ir.model.fields.selection,name:openems.selection__openems_device__producttype__openems-edge +#: model:ir.ui.menu,name:openems.menu_openems_content +msgid "OpenEMS Edge" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_device +msgid "OpenEMS Edge Device" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_openemsconfigupdate +msgid "OpenEMS Edge Device Configuration Update" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_device_tag +msgid "OpenEMS Edge Device Tag" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_device_user_role +msgid "OpenEMS Edge Device User Role" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_setup_protocol_item +msgid "OpenEMS Edge Setup Protocol Entry Item" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_setup_protocol_production_lot +msgid "OpenEMS Edge Setup Protocol Serial Number" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_setup_protocol +msgid "OpenEMS Edge Setup Protocols (IBN)" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_openems_systemmessage +msgid "OpenEMS Edge Systemmessage" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_is_connected +msgid "OpenEMS Is connected" +msgstr "" + +#. module: openems +#: model:ir.actions.report,name:openems.action_openems_setup_protocol_report +msgid "OpenEMS Setup Protocol" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_sum_state_level +msgid "OpenEMS State" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__openems_version +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +msgid "OpenEMS Version" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.device_search_view +#: model_terms:ir.ui.view,arch_db:openems.openems_device_tree +msgid "OpenEMS-Number" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_users__openems_language +msgid "Openems Language" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Ort" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device_user_role__role__owner +#: model:ir.model.fields.selection,name:openems.selection__res_users__global_role__owner +msgid "Owner" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_res_partner__customer_setup_protocols_ids +#: model:ir.model.fields,field_description:openems.field_res_users__customer_setup_protocols_ids +msgid "Owner of OpenEMS Edge" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "PLZ" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__setup_password +msgid "Password for commissioning by the installer" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_setup_protocol_form +msgid "Print" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Product" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__producttype +msgid "Product type" +msgstr "" + +#. module: openems +#: model:res.groups,name:openems.group_openems_reader +msgid "Read access" +msgstr "" + +#. module: openems +#: model:mail.template,subject:openems.registration_email +msgid "Registrierung erfolgreich" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__role +msgid "Role" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__user_role_ids +#: model:ir.model.fields,field_description:openems.field_res_users__device_role_ids +msgid "Roles" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Security" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__lot_id +msgid "Serial Number" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol__productionlot_ids +msgid "Serial Numbers" +msgstr "" + +#. module: openems +#: model:ir.model.constraint,message:openems.constraint_openems_device_unique_stock_production_lot_id +msgid "Serial number needs to be unique" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__setup_protocol_id +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__setup_protocol_id +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Setup Protocol" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_admin_setup_protocol +#: model:ir.model.fields,field_description:openems.field_openems_device__setup_protocol_ids +#: model:ir.ui.menu,name:openems.menu_openems_admin_setup_protocol +#: model_terms:ir.ui.view,arch_db:openems.res_partner_form +msgid "Setup Protocols" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__sequence +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_production_lot__sequence +msgid "Sort" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__res_users__openems_language__es +msgid "Spanish" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Speicherstandort" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Status" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__stock_production_lot_id +msgid "Stock Production Lot" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Straße / Hausnummer" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "System Messages" +msgstr "" + +#. module: openems +#: model:ir.actions.act_window,name:openems.action_openems_systemmessage +#: model:ir.model.fields,field_description:openems.field_openems_device__systemmessage_ids +#: model:ir.ui.menu,name:openems.menu_openems_admin_systemmessages +msgid "Systemmessages" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_device_form +msgid "Systemstatus" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__tag_ids +msgid "Tags" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Telefonnummer" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_systemmessage__text_teaser +msgid "Text Teaser" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__details +msgid "Update Details" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_openemsconfigupdate__teaser +msgid "Update Details Teaser" +msgstr "" + +#. module: openems +#: model:ir.model,name:openems.model_res_users +#: model:ir.model.fields,field_description:openems.field_openems_device_user_role__user_id +msgid "User" +msgstr "" + +#. module: openems +#: model:ir.model.constraint,message:openems.constraint_openems_device_user_role_device_user_uniq +msgid "User already exists for this device." +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.openems_systemmessage_tree +msgid "Users" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__value +msgid "Value" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_setup_protocol_item__view +msgid "View Identifier" +msgstr "" + +#. module: openems +#: model_terms:ir.ui.view,arch_db:openems.report_openems_setup_protocol_template +msgid "Vor- Nachname" +msgstr "" + +#. module: openems +#: model:ir.model.fields.selection,name:openems.selection__openems_device__openems_sum_state_level__warning +msgid "Warning" +msgstr "" + +#. module: openems +#: model:ir.model.fields,field_description:openems.field_openems_device__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: openems +#: model:ir.model.fields,help:openems.field_openems_device__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: openems +#: model:mail.template,subject:openems.setup_protocol_email_customer +#: model:mail.template,subject:openems.setup_protocol_email_installer +msgid "Your OpenEMS setup protocol for {{object.openems_device_id.name}}" +msgstr "" diff --git a/addons/openems/mail/openems/alerting_offline.xml b/addons/openems/mail/openems/alerting_offline.xml new file mode 100644 index 0000000..2d2a38d --- /dev/null +++ b/addons/openems/mail/openems/alerting_offline.xml @@ -0,0 +1,275 @@ + + + + + E-Mail Offline-Alerting + + ]]> + {{object.user_id.partner_id.id}} + OpenEMS Alert - Edge is offline + + + + + OpenEMS Alert - Edge is offline + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+

+

+ +
+
+ + + , + + Ms/Mr , + Customer, +
+
+
Your OpenEMS Edge with number is offline since: +
+
+
+ + + + + + + (UTC) + + +
+
+ + + + + + + + + + + + +
Info
+ OpenEMS-Version + + + + + UNKNOWN +
+ Type + + + + + + + +
+
+
Best Regards
+
+
OpenEMS Association e.V.
+
+
+ +
+
+ +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+
If you do no longer wish to receive email notifications, you can disable the feature here .
+
+
This is an automatically generated message. Please do not reply to this message!
+
+ + + + + + +
+ OpenEMS Logo +
+
+

+

+ +
+
+ +
+
+ +
+ + + + ]]> +
+
+
+
diff --git a/addons/openems/mail/openems/alerting_sum_state.xml b/addons/openems/mail/openems/alerting_sum_state.xml new file mode 100644 index 0000000..5ac49f6 --- /dev/null +++ b/addons/openems/mail/openems/alerting_sum_state.xml @@ -0,0 +1,268 @@ + + + + + E-Mail SumState Alerting + + ]]> + {{object.user_id.partner_id.id}} + OpenEMS Alert - Edge is in {{object.device_id.openems_sum_state_level}} State + + + + + OpenEMS Alert - Edge is in fault + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+

+

+ +
+
+ + + , + + Ms/Mr , + Customer, +
+
+
Your OpenEMS Edge with number is in a continuous + + + + + + + UNKNOWN + state! +
+
+ + + + + + + + + + + + +
Info
+ OpenEMS-Version + + + + + UNKNOWN +
+ Type + + + + + + + +
+
+
Best Regards
+
+
OpenEMS Association e.V.
+
+
+ +
+
+ +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+
If you do no longer wish to receive email notifications, you can disable the feature here .
+
+
This is an automatically generated message. Please do not reply to this message!
+
+ + + + + + +
+ OpenEMS Logo +
+
+

+

+ +
+
+ +
+
+ +
+ + + + ]]> +
+
+
+
diff --git a/addons/openems/mail/openems/setup_protocol_customer.xml b/addons/openems/mail/openems/setup_protocol_customer.xml new file mode 100644 index 0000000..3b1e781 --- /dev/null +++ b/addons/openems/mail/openems/setup_protocol_customer.xml @@ -0,0 +1,170 @@ + + + + + E-Mail setup protocol for customer + + ]]> + {{object.customer_id.id}} + Your OpenEMS setup protocol for {{object.openems_device_id.name}} + false + + + + + Your OpenEMS Edge setup protocol + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+

+

+ +
+
Welcome to OpenEMS,
+
+
please find your setup protocol for OpenEMS Edge attached.
+
+
Best Regards
+
+
OpenEMS Association e.V.
+
+
+ +
+
+ +
+
+ + + + ]]> +
+
+
+
diff --git a/addons/openems/mail/openems/setup_protocol_installer.xml b/addons/openems/mail/openems/setup_protocol_installer.xml new file mode 100644 index 0000000..13afd9d --- /dev/null +++ b/addons/openems/mail/openems/setup_protocol_installer.xml @@ -0,0 +1,173 @@ + + + + + E-Mail setup protocol for installer + + ]]> + {{object.installer_id.id}} + Your OpenEMS setup protocol for {{object.openems_device_id.name}} + false + + + + + OpenEMS setup protocol + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+

+

+ +
+
Dear + , +
+
+
please find the setup protocol for your customer attached. +
+
+
Best Regards
+
+
OpenEMS Association e.V.
+
+
+ +
+
+ +
+
+ + + + ]]> +
+
+
+
diff --git a/addons/openems/mail/openems/user_registration.xml b/addons/openems/mail/openems/user_registration.xml new file mode 100644 index 0000000..cfd4183 --- /dev/null +++ b/addons/openems/mail/openems/user_registration.xml @@ -0,0 +1,197 @@ + + + + + E-Mail Kunden Registrierung + + ]]> + {{object.id}} + Registrierung erfolgreich + false + + + + + Registrierung erfolgreich + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+

+

+ +
+
Guten Tag + , +
+
+
Ihr Zugang zu OpenEMS UI wurde erstellt.
+
+
Sie können sich auf OpenEMS UI einloggen, um auf das Online-Monitoring zuzugreifen.
+
+ + + + + + + + + + + + +
Ihre Zugangsdaten:
E-Mail + +
Passwort + +
+
+
Best Regards
+
+
OpenEMS Association e.V.
+
+
+ +
+
+ +
+
+ + + + ]]> +
+
+
+
diff --git a/addons/openems/migrations/16.0.1.0.1/post-migrate.py b/addons/openems/migrations/16.0.1.0.1/post-migrate.py new file mode 100644 index 0000000..1eab6fe --- /dev/null +++ b/addons/openems/migrations/16.0.1.0.1/post-migrate.py @@ -0,0 +1,9 @@ +def migrate(cr, version): + cr.execute(""" + INSERT INTO openems_alerting (device_id, device_name, user_id, user_login, offline_delay, warning_delay, fault_delay, offline_last_notification) + SELECT device_id, dev.name, user_id, usr.login, time_to_wait, 0, 0, last_notification + FROM openems_alerting_migrate AS migrate + LEFT JOIN openems_device AS dev ON dev.id = migrate.device_id + LEFT JOIN res_users AS usr ON usr.id = migrate.user_id + """) + cr.execute('DROP TABLE openems_alerting_migrate') diff --git a/addons/openems/migrations/16.0.1.0.1/pre-migrate.py b/addons/openems/migrations/16.0.1.0.1/pre-migrate.py new file mode 100644 index 0000000..341e4a5 --- /dev/null +++ b/addons/openems/migrations/16.0.1.0.1/pre-migrate.py @@ -0,0 +1,7 @@ +def migrate(cr, version): + cr.execute(""" + SELECT device_id, user_id, time_to_wait, last_notification + INTO openems_alerting_migrate + FROM openems_device_user_role + WHERE time_to_wait > 0; + """) diff --git a/addons/openems/models/__init__.py b/addons/openems/models/__init__.py new file mode 100644 index 0000000..c4a7e59 --- /dev/null +++ b/addons/openems/models/__init__.py @@ -0,0 +1 @@ +from . import device, partner, setup_protocol, user, stock_production_lot diff --git a/addons/openems/models/device.py b/addons/openems/models/device.py new file mode 100644 index 0000000..bdbda80 --- /dev/null +++ b/addons/openems/models/device.py @@ -0,0 +1,321 @@ +from odoo import api, fields, models, exceptions, _ +from datetime import datetime +from odoo.exceptions import ValidationError +import random +import re +import string + +class Device(models.Model): + _name = "openems.device" + _description = "OpenEMS Edge Device" + _inherit = "mail.thread" + _order = "name_number asc" + _sql_constraints = [ + ("unique_name", "unique(name)", "Name needs to be unique"), + ("unique_stock_production_lot_id", "unique(stock_production_lot_id)", + "Serial number needs to be unique") + ] + + name = fields.Char(required=True) + active = fields.Boolean("Active", default=True, tracking=True) + comment = fields.Char(tracking=True) + internalnote = fields.Text("Internal note", tracking=True) + tag_ids = fields.Many2many("openems.device_tag", string="Tags", tracking=True) + monitoring_url = fields.Char( + "Online-Monitoring", compute="_compute_monitoring_url", store=False + ) + stock_production_lot_id = fields.Many2one("stock.lot") + first_setup_protocol_date = fields.Datetime( + "First Setup Protocol Date", compute="_compute_first_setup_protocol" + ) + manual_setup_date = fields.Datetime("Manual Setup Date") + + @api.depends("setup_protocol_ids", "manual_setup_date") + def _compute_first_setup_protocol(self): + for rec in self: + if rec.manual_setup_date: + rec.first_setup_protocol_date = rec.manual_setup_date + elif len(rec.setup_protocol_ids) > 0: + rec.first_setup_protocol_date = rec.setup_protocol_ids[ + (len(rec.setup_protocol_ids) - 1) + ]["create_date"] + else: + rec.first_setup_protocol_date = None + + @api.depends("name") + def _compute_monitoring_url(self): + # Corrected the parameter key to 'edge_monitoring_url' + base_url = self.env["ir.config_parameter"].sudo().get_param("edge_monitoring_url", default='#') + for rec in self: + if isinstance(rec.name, str) and rec.name: + # Ensuring there is a '/' between base_url and rec.name if it's not already present + separator = '' if base_url.endswith('/') else '/' + rec.monitoring_url = base_url + separator + rec.name + "/live" + else: + rec.monitoring_url = base_url + + producttype = fields.Selection( + [ + ("openems-edge", "OpenEMS Edge"), + ], + "Product type", + tracking=True, + ) + emshardware = fields.Selection([], "EMS Hardware", tracking=True) + oem = fields.Selection( + [ + ("openems", "OpenEMS"), + ], + "OEM Branding", + default="openems", + ) + + # Settings + openems_config = fields.Text("OpenEMS Config Full") + openems_config_components = fields.Text("OpenEMS Config") + openems_version = fields.Char("OpenEMS Version", tracking=True) + + # Security + setup_password = fields.Char( + "Installation Key", + help="Password for commissioning by the installer", + ) + apikey = fields.Char("API-Key", required=True, tracking=True) + + # 'openems_sum_state_level' is updated by OpenEMS Backend + openems_sum_state_level = fields.Selection( + [("ok", "Ok"), ("info", "Info"), ("warning", "Warning"), ("fault", "Fault")], + "OpenEMS State", + ) + # 'openems_is_connected' is updated by OpenEMS Backend + openems_is_connected = fields.Boolean("OpenEMS Is connected") + + # System Status + lastmessage = fields.Datetime("Last message") + lastupdate = fields.Datetime("Last data update") + + # Verknüpfungen + systemmessage_ids = fields.One2many( + "openems.systemmessage", "device_id", string="System Messages" + ) + user_role_ids = fields.One2many( + "openems.device_user_role", "device_id", string="Roles", tracking=True + ) + alerting_settings = fields.One2many( + "openems.alerting", "device_id", string="Alerting", tracking=True + ) + openems_config_update_ids = fields.One2many( + "openems.openemsconfigupdate", "device_id", string="OpenEMS Config Updates" + ) + setup_protocol_ids = fields.One2many( + "openems.setup_protocol", "device_id", "Setup Protocols" + ) + + # Helper fields + name_number = fields.Integer(compute="_compute_name_number", store="True") + + @api.depends("name") + def _compute_name_number(self): + for rec in self: + rec.name_number = int(rec.name[4:]) if rec.name.startswith("edge") else -1 + + def _get_openems_state_number(self, string): + state = 0 + if string == "info": + state = 1 + elif string == "warning": + state = 2 + elif string == "fault": + state = 3 + return state + + def write(self, vals): + """Prohibit to change name field after creation.""" + if 'name' in vals: + for record in self: + if record.id and record.name != vals['name']: + self.env.cr.execute(""" + SELECT EXISTS ( + SELECT 1 FROM openems_device + WHERE name = %s AND id != %s + ) + """, (vals['name'], record.id)) + exists = self.env.cr.fetchone()[0] + if exists: + # This means there's already a device with the intended new name + raise exceptions.UserError( + "The name '{}' is already in use or does not follow the required pattern.".format( + vals['name'])) + + # If you simply want to prevent name changes, the following UserError suffices + raise exceptions.UserError("The name of the device cannot be changed after creation.") + return super(Device, self).write(vals) + + @api.model + def create(self, vals): + + # Generate setup password if not provided + if 'setup_password' not in vals or not vals['setup_password']: + vals['setup_password'] = self._generate_unique_setup_password() + + # Generate API key if not provided + if 'apikey' not in vals or not vals['apikey']: + vals['apikey'] = self._generate_api_key() + + return super(Device, self).create(vals) + + def _generate_unique_setup_password(self): + is_unique = False + setup_password = '' + while not is_unique: + # Generate a random setup password + raw_password = ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)) + setup_password = '-'.join([raw_password[i:i + 4] for i in range(0, len(raw_password), 4)]) + # Check if the generated setup password already exists + existing = self.search_count([('setup_password', '=', setup_password)]) + # If the password does not exist, it is unique, and we can exit the loop + if existing == 0: + is_unique = True + return setup_password + + def _generate_api_key(self): + # Initialize a flag to indicate whether the generated key is unique + is_unique = False + api_key = '' + while not is_unique: + # Generate a random API key + api_key = ''.join(random.choices(string.ascii_letters + string.digits, k=20)) + # Check if the generated API key already exists + existing = self.search_count([('apikey', '=', api_key)]) + # If the key does not exist, it is unique, and we can exit the loop + if existing == 0: + is_unique = True + return api_key + + @api.onchange('setup_password') + def _check_setup_password_format(self): + for record in self: + if not record.setup_password: + continue + if not re.match(r"^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$", record.setup_password): + raise ValidationError("The device ID must be formatted as XXXX-XXXX-XXXX-XXXX") + + @api.onchange('apikey') + def _check_api_key_uniqueness(self): + for record in self: + if record.apikey: + # Prepare the domain for searching duplicates + domain = [('apikey', '=', record.apikey)] + # If the record is already saved (has a valid database ID), exclude it from the search + if record.id and isinstance(record.id, (int,)): + domain.append(('id', '!=', record.id)) + # Check if any other records with the same API key exist + existing = self.search_count(domain) + # If there are duplicates, raise a ValidationError + if existing: + raise ValidationError( + _("The API key already exists and must be unique. Please choose a different API key.")) + + +class DeviceTag(models.Model): + _name = "openems.device_tag" + _description = "OpenEMS Edge Device Tag" + name = fields.Char(required=True) + + +class DeviceUserRole(models.Model): + _name = "openems.device_user_role" + _description = "OpenEMS Edge Device User Role" + _sql_constraints = [ + ( + "device_user_uniq", + "unique(device_id, user_id)", + "User already exists for this device.", + ), + ] + device_id = fields.Many2one("openems.device", string="OpenEMS Edge") + user_id = fields.Many2one("res.users", string="User") + role = fields.Selection( + [ + ("admin", "Admin"), + ("installer", "Installer"), + ("owner", "Owner"), + ("guest", "Guest"), + ], + default="guest", + required=True, + ) + + +class OpenemsConfigUpdate(models.Model): + _name = "openems.openemsconfigupdate" + _description = "OpenEMS Edge Device Configuration Update" + _order = "create_date desc" + + device_id = fields.Many2one("openems.device", string="OpenEMS Edge") + teaser = fields.Text("Update Details Teaser") + details = fields.Html("Update Details") + + +class Systemmessage(models.Model): + _name = "openems.systemmessage" + _description = "OpenEMS Edge Systemmessage" + _order = "create_date desc" + + timestamp = fields.Datetime("Creation date") + device_id = fields.Many2one("openems.device", string="OpenEMS Edge") + text = fields.Text("Message") + text_teaser = fields.Char(compute="_compute_text_teaser") + + @api.depends("text") + def _compute_text_teaser(self): + for rec in self: + # get up to 100 characters from first line + rec.text_teaser = rec.text.splitlines()[0][0:100] if rec.text else False + +class Alerting(models.Model): + _name = "openems.alerting" + _description = "OpenEMS Edge AlertingSettings" + _sql_constraints = [ + ( + "device_user_uniq", + "unique(device_id, user_id)", + "User already has Alerting Settings.", + ), + ] + + device_id = fields.Many2one("openems.device", string="OpenEMS Edge") + user_id = fields.Many2one("res.users", string="User") + + offline_delay = fields.Integer(string="Offline Notification", default=1440) + warning_delay = fields.Integer(string="Warning Notification", default=1440) + fault_delay = fields.Integer(string="Fault Notification", default=1440) + + offline_last_notification = fields.Datetime(string="Last Offline notification sent") + sum_state_last_notification = fields.Datetime(string="Last SumState notification sent") + + device_name = fields.Text(compute="_compute_device_name", store="True") + user_login = fields.Text(compute="_compute_user_login", store="True") + + user_role = fields.Selection( + [("admin", "Admin"), ("installer", "Installer"), ("owner", "Owner"), ("guest", "Guest"),], + compute="_compute_user_role", store="False") + + @api.depends("device_id") + def _compute_device_name(self): + for rec in self: + rec.device_name = rec.device_id.name; + + @api.depends("user_id") + def _compute_user_login(self): + for rec in self: + rec.user_login = rec.user_id.login; + + @api.depends("user_id", "device_id") + def _compute_user_role(self): + for rec in self: + user_role: DeviceUserRole = rec.user_id.device_role_ids.search([('device_id','=',rec.device_id.id)]) + if user_role: + return user_role.role + else: + return rec.user_id.global_role diff --git a/addons/openems/models/partner.py b/addons/openems/models/partner.py new file mode 100644 index 0000000..37ab8e5 --- /dev/null +++ b/addons/openems/models/partner.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + installer_setup_protocols_ids = fields.One2many( + "openems.setup_protocol", "installer_id", "Installed OpenEMS Edge" + ) + customer_setup_protocols_ids = fields.One2many( + "openems.setup_protocol", "customer_id", "Owner of OpenEMS Edge" + ) diff --git a/addons/openems/models/setup_protocol.py b/addons/openems/models/setup_protocol.py new file mode 100644 index 0000000..548576e --- /dev/null +++ b/addons/openems/models/setup_protocol.py @@ -0,0 +1,49 @@ +from odoo import fields, models + + +class SetupProtocol(models.Model): + _name = "openems.setup_protocol" + _description = "OpenEMS Edge Setup Protocols (IBN)" + _order = "create_date desc" + + customer_id = fields.Many2one("res.partner", "Customer", required=True) + different_location_id = fields.Many2one("res.partner", "Different Location") + installer_id = fields.Many2one("res.partner", "Installer", required=True) + device_id = fields.Many2one("openems.device", "OpenEMS Edge", required=True) + productionlot_ids = fields.One2many( + "openems.setup_protocol_production_lot", "setup_protocol_id", "Serial Numbers" + ) + item_ids = fields.One2many( + "openems.setup_protocol_item", "setup_protocol_id", "Entry Items" + ) + + +class SetupProtocolProductionLot(models.Model): + _name = "openems.setup_protocol_production_lot" + _description = "OpenEMS Edge Setup Protocol Serial Number" + _order = "setup_protocol_id, category, sequence asc" + + sequence = fields.Integer("Sort") + category = fields.Char("Category") + name = fields.Char("Name") + lot_id = fields.Many2one("stock.production.lot", "Serial Number") + setup_protocol_id = fields.Many2one( + "openems.setup_protocol", "Setup Protocol", ondelete="cascade" + ) + + +class SetupProtocolItem(models.Model): + _name = "openems.setup_protocol_item" + _description = "OpenEMS Edge Setup Protocol Entry Item" + _order = "setup_protocol_id, category, sequence asc" + + sequence = fields.Integer("Sort") + category = fields.Char("Category") + name = fields.Char("Name") + value = fields.Char("Value") + setup_protocol_id = fields.Many2one( + "openems.setup_protocol", "Setup Protocol", ondelete="cascade" + ) + view = fields.Char("View Identifier") + field = fields.Char("Field Identifier") + diff --git a/addons/openems/models/stock_production_lot.py b/addons/openems/models/stock_production_lot.py new file mode 100644 index 0000000..37ebb22 --- /dev/null +++ b/addons/openems/models/stock_production_lot.py @@ -0,0 +1,23 @@ +from odoo import fields, models, api + + +class ProductionLot(models.Model): + _inherit = "stock.lot" + + device_id = fields.Many2one( + 'openems.device', compute='compute_device_id', inverse='device_inverse') + device_ids = fields.One2many('openems.device', 'stock_production_lot_id') + + @api.depends('device_ids') + def compute_device_id(self): + if len(self.device_ids) > 0: + self.device_id = self.device_ids[0] + + def device_inverse(self): + if len(self.device_ids) > 0: + if len(self.device_id.stock_production_lot_id) > 0: + raise ValueError("A serial number has already been assigned to the device") + + device = self.env['openems.device'].browse(self.device_ids[0].id) + device.stock_production_lot_id = False + self.device_id.stock_production_lot_id = self diff --git a/addons/openems/models/user.py b/addons/openems/models/user.py new file mode 100644 index 0000000..321dd87 --- /dev/null +++ b/addons/openems/models/user.py @@ -0,0 +1,37 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + branding_partner_id = fields.Many2one("res.partner", string="Branding Partner") + global_role = fields.Selection( + [ + ("admin", "Admin"), + ("installer", "Installer"), + ("owner", "Owner"), + ("guest", "Guest"), + ], + default="guest", + required=True, + ) + device_role_ids = fields.One2many( + "openems.device_user_role", "user_id", string="Roles" + ) + alerting_settings = fields.One2many( + "openems.alerting", "user_id", string="Alerting" + ) + openems_language = fields.Selection( + [ + ("EN", "English"), + ("DE", "German"), + ("CZ", "Czech"), + ("NL", "Dutch"), + ("ES", "Spanish"), + ("FR", "French"), + ("HU", "Hungarian"), + ("JA", "Japanese"), + ], + default="DE", + required=True, + ) diff --git a/addons/openems/report/setup_protocol.xml b/addons/openems/report/setup_protocol.xml new file mode 100644 index 0000000..c9db4ec --- /dev/null +++ b/addons/openems/report/setup_protocol.xml @@ -0,0 +1,311 @@ + + + + OpenEMS Setup Protocol + openems.setup_protocol + qweb-pdf + openems.report_openems_setup_protocol_template + ('IBN-' + object.openems_device_id.name + '-' + object.create_date.strftime('%d.%m.%Y')) + + + diff --git a/addons/openems/security/ir.model.access.csv b/addons/openems/security/ir.model.access.csv new file mode 100644 index 0000000..1ccc0f1 --- /dev/null +++ b/addons/openems/security/ir.model.access.csv @@ -0,0 +1,31 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_openems_device_portal,access_openems_device_portal,openems.model_openems_device,base.group_portal,1,0,0,0 +access_openems_device_user,access_openems_device_user,openems.model_openems_device,base.group_user,1,0,0,0 +access_openems_device_manager,access_openems_device_manager,openems.model_openems_device,openems.group_openems_manager,1,1,1,0 +access_openems_device_tag_portal,access_openems_device_tag_portal,openems.model_openems_device_tag,base.group_portal,1,0,0,0 +access_openems_device_tag_user,access_openems_device_tag_user,openems.model_openems_device_tag,base.group_user,1,0,0,0 +access_openems_device_tag_manager,access_openems_device_tag_manager,openems.model_openems_device_tag,openems.group_openems_manager,1,1,1,1 +access_openems_device_user_role_portal,access_openems_device_user_role_portal,openems.model_openems_device_user_role,base.group_portal,1,0,0,0 +access_openems_device_user_role_user,access_openems_device_user_role_user,openems.model_openems_device_user_role,base.group_user,1,0,0,0 +access_openems_device_user_role_manager,access_openems_device_user_role_manager,openems.model_openems_device_user_role,openems.group_openems_manager,1,1,1,1 +access_openems_alerting_portal,access_openems_alerting_portal,openems.model_openems_alerting,base.group_portal,1,0,0,0 +access_openems_alerting_user,access_openems_alerting_user,openems.model_openems_alerting,base.group_user,1,0,0,0 +access_openems_alerting_manager,access_openems_alerting_manager,openems.model_openems_alerting,openems.group_openems_manager,1,1,1,1 +access_openems_systemmessage_portal,access_openems_systemmessage_portal,openems.model_openems_systemmessage,base.group_portal,1,0,0,0 +access_openems_systemmessage_user,access_openems_systemmessage_user,openems.model_openems_systemmessage,base.group_user,1,0,0,0 +access_openems_systemmessage_manager,access_openems_systemmessage_manager,openems.model_openems_systemmessage,openems.group_openems_manager,1,1,1,1 +access_openems_openemsconfigupdate_portal,access_openems_openemsconfigupdate_portal,openems.model_openems_openemsconfigupdate,base.group_portal,1,0,0,0 +access_openems_openemsconfigupdate_user,access_openems_openemsconfigupdate_user,openems.model_openems_openemsconfigupdate,base.group_user,1,0,0,0 +access_openems_openemsconfigupdate_manager,access_openems_openemsconfigupdate_manager,openems.model_openems_openemsconfigupdate,openems.group_openems_manager,1,1,1,1 +access_openems_setup_protocol_portal,access_openems_setup_protocol_portal,openems.model_openems_setup_protocol,base.group_portal,1,0,0,0 +access_openems_setup_protocol_user,access_openems_setup_protocol_user,openems.model_openems_setup_protocol,base.group_user,1,0,0,0 +access_openems_setup_protocol_manager,access_openems_setup_protocol_manager,openems.model_openems_setup_protocol,openems.group_openems_manager,1,1,1,1 +access_openems_setup_protocol_production_lot_portal,access_openems_setup_protocol_production_lot_portal,openems.model_openems_setup_protocol_production_lot,base.group_portal,1,0,0,0 +access_openems_setup_protocol_production_lot_user,access_openems_setup_protocol_production_lot_user,openems.model_openems_setup_protocol_production_lot,base.group_user,1,0,0,0 +access_openems_setup_protocol_production_lot_manager,access_openems_setup_protocol_production_lot_manager,openems.model_openems_setup_protocol_production_lot,openems.group_openems_manager,1,1,1,1 +access_openems_setup_protocol_item_portal,access_openems_setup_protocol_item_portal,openems.model_openems_setup_protocol_item,base.group_portal,1,0,0,0 +access_openems_setup_protocol_item_user,access_openems_setup_protocol_item_user,openems.model_openems_setup_protocol_item,base.group_user,1,0,0,0 +access_openems_setup_protocol_item_manager,access_openems_setup_protocol_item_manager,openems.model_openems_setup_protocol_item,openems.group_openems_manager,1,1,1,1 +access_openems_production_lot_portal,access_openems_production_lot_portal,stock.model_stock_lot,base.group_portal,1,0,0,0 +access_openems_production_lot_user,access_openems_production_lot_user,stock.model_stock_lot,base.group_user,1,0,0,0 +access_openems_production_lot_manager,access_openems_production_lot_manager,stock.model_stock_lot,openems.group_openems_manager,1,0,0,0 diff --git a/addons/openems/security/openems.xml b/addons/openems/security/openems.xml new file mode 100644 index 0000000..ef87c2f --- /dev/null +++ b/addons/openems/security/openems.xml @@ -0,0 +1,54 @@ + + + + OpenEMS + 30 + + + + Read access + Members of this group have reading access to all devices + + + + + Manager + Members of this group can manage all devices + + + + + + + Website: Show only approved devices to Portal and User + + ['|', ('user_role_ids.user_id','in',[user.id]), ('alerting_settings.user_id','in',[user.id])] + + + + + + + + + Website: Show all devices to readers group + + [(1,'=',1)] + + + + + + + + diff --git a/addons/openems/setup/.setuptools-odoo-make-default-ignore b/addons/openems/setup/.setuptools-odoo-make-default-ignore new file mode 100644 index 0000000..207e615 --- /dev/null +++ b/addons/openems/setup/.setuptools-odoo-make-default-ignore @@ -0,0 +1,2 @@ +# addons listed in this file are ignored by +# setuptools-odoo-make-default (one addon per line) diff --git a/addons/openems/setup/README b/addons/openems/setup/README new file mode 100644 index 0000000..a63d633 --- /dev/null +++ b/addons/openems/setup/README @@ -0,0 +1,2 @@ +To learn more about this directory, please visit +https://pypi.python.org/pypi/setuptools-odoo diff --git a/addons/openems/static/description/icon.png b/addons/openems/static/description/icon.png new file mode 100644 index 0000000..ee0aed1 Binary files /dev/null and b/addons/openems/static/description/icon.png differ diff --git a/addons/openems/static/mail/OpenEMS-Logo.jpg b/addons/openems/static/mail/OpenEMS-Logo.jpg new file mode 100644 index 0000000..13ae0fb Binary files /dev/null and b/addons/openems/static/mail/OpenEMS-Logo.jpg differ diff --git a/addons/openems/views/device.xml b/addons/openems/views/device.xml new file mode 100644 index 0000000..e74b1cb --- /dev/null +++ b/addons/openems/views/device.xml @@ -0,0 +1,320 @@ + + + + + Device: Tree + openems.device + + + + + + + + + + + + + + + + + Device: Search + openems.device + + + + + + + + + + + + + + + + + + + + + Device: Form + openems.device + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+
+
+ + + Device DeviceUserRole: Tree + openems.device_user_role + + + + + + + + + + Devices + ir.actions.act_window + openems.device + tree,form + + + + + + Device Configuration Updates + openems.openemsconfigupdate + + + + Device Configuration Updates: Tree + openems.openemsconfigupdate + + + + + + + + + + + Device Configuration Updates: Form + openems.openemsconfigupdate + +
+ + + + + + + +
+
+
+ + + + Systemmessages + openems.systemmessage + + + + Device Systemmessage: Tree + openems.systemmessage + + + + + + + + + + + Device Systemmessage: Form + openems.systemmessage + +
+ + + + + + + +
+
+
+ + + + + + + + +
diff --git a/addons/openems/views/partner.xml b/addons/openems/views/partner.xml new file mode 100644 index 0000000..874530e --- /dev/null +++ b/addons/openems/views/partner.xml @@ -0,0 +1,28 @@ + + + + OpenEMS Partner: Form + res.partner + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/openems/views/setup_protocol.xml b/addons/openems/views/setup_protocol.xml new file mode 100644 index 0000000..bf2eee2 --- /dev/null +++ b/addons/openems/views/setup_protocol.xml @@ -0,0 +1,82 @@ + + + + SetupProtocol: Form + openems.setup_protocol + +
+ + + + + + + + + diff --git a/addons/web_m2x_options/static/src/components/form.esm.js b/addons/web_m2x_options/static/src/components/form.esm.js new file mode 100644 index 0000000..ecb37d2 --- /dev/null +++ b/addons/web_m2x_options/static/src/components/form.esm.js @@ -0,0 +1,404 @@ +/** @odoo-module **/ + +import { + Many2ManyTagsField, + Many2ManyTagsFieldColorEditable, +} from "@web/views/fields/many2many_tags/many2many_tags_field"; + +import {Dialog} from "@web/core/dialog/dialog"; +import {FormController} from "@web/views/form/form_controller"; +import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog"; +import {Many2OneAvatarField} from "@web/views/fields/many2one_avatar/many2one_avatar_field"; +import {Many2OneBarcodeField} from "@web/views/fields/many2one_barcode/many2one_barcode_field"; +import {Many2OneField} from "@web/views/fields/many2one/many2one_field"; +import {ReferenceField} from "@web/views/fields/reference/reference_field"; +import {X2ManyField} from "@web/views/fields/x2many/x2many_field"; +import {isX2Many} from "@web/views/utils"; +import {is_option_set} from "@web_m2x_options/components/relational_utils.esm"; +import {patch} from "@web/core/utils/patch"; +import {sprintf} from "@web/core/utils/strings"; +import {useService} from "@web/core/utils/hooks"; + +const {Component} = owl; + +/** + * Patch Many2ManyTagsField + **/ +patch(Many2ManyTagsField.prototype, "web_m2x_options.Many2ManyTagsField", { + setup() { + this._super(...arguments); + this.actionService = useService("action"); + }, + /** + * @override + */ + getTagProps(record) { + const props = this._super(...arguments); + props.onClick = (ev) => this.onMany2ManyBadgeClick(ev, record); + return props; + }, + async onMany2ManyBadgeClick(event, record) { + var self = this; + if (self.props.open) { + var context = self.context; + var id = record.data.id; + if (self.props.readonly) { + event.preventDefault(); + event.stopPropagation(); + const action = await self.orm.call( + self.props.relation, + "get_formview_action", + [[id]], + {context: context} + ); + self.actionService.doAction(action); + } else { + const view_id = await self.orm.call( + self.props.relation, + "get_formview_id", + [[id]], + {context: context} + ); + + const write_access = await self.orm.call( + self.props.relation, + "check_access_rights", + [], + {operation: "write", raise_exception: false} + ); + var can_write = self.props.canWrite; + self.dialog.add(FormViewDialog, { + resModel: self.props.relation, + resId: id, + context: context, + title: self.env._t("Open: ") + self.string, + viewId: view_id, + mode: !can_write || !write_access ? "readonly" : "edit", + onRecordSaved: () => self.props.value.model.load(), + }); + } + } + }, +}); + +Many2ManyTagsField.props = { + ...Many2ManyTagsField.props, + open: {type: Boolean, optional: true}, + canWrite: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +const Many2ManyTagsFieldExtractProps = Many2ManyTagsField.extractProps; +Many2ManyTagsField.extractProps = ({attrs, field}) => { + const canOpen = Boolean(attrs.options.open); + const canWrite = attrs.can_write && Boolean(JSON.parse(attrs.can_write)); + return Object.assign(Many2ManyTagsFieldExtractProps({attrs, field}), { + open: canOpen, + canWrite: canWrite, + nodeOptions: attrs.options, + }); +}; + +/** + * Many2ManyTagsFieldColorEditable + **/ +patch( + Many2ManyTagsFieldColorEditable.prototype, + "web_m2x_options.Many2ManyTagsFieldColorEditable", + { + async onBadgeClick(event, record) { + if (this.props.canEditColor && !this.props.open) { + this._super(...arguments); + } + if (this.props.open) { + Many2ManyTagsField.prototype.onMany2ManyBadgeClick.bind(this)( + event, + record + ); + } + }, + } +); + +Many2ManyTagsFieldColorEditable.props = { + ...Many2ManyTagsFieldColorEditable.props, + open: {type: Boolean, optional: true}, + canWrite: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * CreateConfirmationDialog + * New customized component for Many2One Field + **/ + +class CreateConfirmationDialog extends Component { + get title() { + return sprintf(this.env._t("New: %s"), this.props.name); + } + + async onCreate() { + await this.props.create(); + this.props.close(); + } + async onCreateEdit() { + await this.props.createEdit(); + this.props.close(); + } +} +CreateConfirmationDialog.components = {Dialog}; +CreateConfirmationDialog.template = + "web_m2x_options.Many2OneField.CreateConfirmationDialog"; + +/** + * Many2OneField + **/ + +patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", { + setup() { + this._super(...arguments); + this.ir_options = Component.env.session.web_m2x_options; + }, + /** + * @override + */ + get Many2XAutocompleteProps() { + const props = this._super(...arguments); + return { + ...props, + searchLimit: this.props.searchLimit, + searchMore: this.props.searchMore, + canCreate: this.props.canCreate, + nodeOptions: this.props.nodeOptions, + }; + }, + + async openConfirmationDialog(request) { + var m2o_dialog_opt = + is_option_set(this.props.nodeOptions.m2o_dialog) || + (_.isUndefined(this.props.nodeOptions.m2o_dialog) && + is_option_set(this.ir_options["web_m2x_options.m2o_dialog"])) || + (_.isUndefined(this.props.nodeOptions.m2o_dialog) && + _.isUndefined(this.ir_options["web_m2x_options.m2o_dialog"])); + if (this.props.canCreate && this.state.isFloating && m2o_dialog_opt) { + return new Promise((resolve, reject) => { + this.addDialog(CreateConfirmationDialog, { + value: request, + name: this.props.string, + create: async () => { + try { + await this.quickCreate(request); + resolve(); + } catch (e) { + reject(e); + } + }, + createEdit: async () => { + try { + await this.quickCreate(request); + await this.props.record.model.load(); + this.openMany2X({ + resId: this.props.value[0], + context: this.user_context, + }); + resolve(); + } catch (e) { + reject(e); + } + }, + }); + }); + } + }, +}); + +const Many2OneFieldExtractProps = Many2OneField.extractProps; +Many2OneField.extractProps = ({attrs, field}) => { + return Object.assign(Many2OneFieldExtractProps({attrs, field}), { + searchLimit: attrs.options.limit, + searchMore: attrs.options.search_more, + nodeOptions: attrs.options, + }); +}; + +Many2OneField.props = { + ...Many2OneField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override ReferenceField + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + */ + +ReferenceField.props = { + ...ReferenceField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override Many2OneBarcodeField + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + */ + +Many2OneBarcodeField.props = { + ...Many2OneBarcodeField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override Many2OneAvatarField + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + */ +Many2OneAvatarField.props = { + ...Many2OneAvatarField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override mailing_m2o_filter + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + * This component is in module mass_mailing as optional module, + * So need to import dynamic way + */ +try { + (async () => { + // Make sure component mailing_m2o_filter in mass mailing module loaded + const installed_mass_mailing = await odoo.ready( + "@mass_mailing/js/mailing_m2o_filter" + ); + if (installed_mass_mailing) { + const {FieldMany2OneMailingFilter} = await odoo.runtimeImport( + "@mass_mailing/js/mailing_m2o_filter" + ); + FieldMany2OneMailingFilter.props = { + ...FieldMany2OneMailingFilter.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, + }; + } + })(); +} catch { + console.log( + "Ignore overriding props of component mailing_m2o_filter since the module is not installed" + ); +} + +/** + * X2ManyField + **/ +patch(X2ManyField.prototype, "web_m2x_options.X2ManyField", { + /** + * @override + */ + async openRecord(record) { + var self = this; + var open = this.props.open; + if (open && self.props.readonly) { + var res_id = record.data.id; + const action = await self.env.model.orm.call( + self.props.value.resModel, + "get_formview_action", + [[res_id]] + ); + return self.env.model.actionService.doAction(action); + } + return this._super.apply(this, arguments); + }, +}); + +const X2ManyFieldExtractProps = X2ManyField.extractProps; +X2ManyField.extractProps = ({attrs}) => { + const canOpen = Boolean(attrs.options.open); + return Object.assign(X2ManyFieldExtractProps({attrs}), { + open: canOpen, + }); +}; + +X2ManyField.props = { + ...X2ManyField.props, + open: {type: Boolean, optional: true}, +}; + +/** + * FormController + **/ +patch(FormController.prototype, "web_m2x_options.FormController", { + /** + * @override + */ + setup() { + var self = this; + this._super(...arguments); + + /** Due to problem of 2 onWillStart in native web core + * (see: https://github.com/odoo/odoo/blob/16.0/addons/web/static/src/views/model.js#L142) + * do the trick to override beforeLoadResolver here to customize viewLimit + */ + this.superBeforeLoadResolver = this.beforeLoadResolver; + this.beforeLoadResolver = async () => { + await self._setSubViewLimit(); + self.superBeforeLoadResolver(); + }; + }, + /** + * @override + * add more method to add subview limit on formview + */ + async _setSubViewLimit() { + const ir_options = Component.env.session.web_m2x_options; + + const activeFields = this.archInfo.activeFields, + fields = this.props.fields, + isSmall = this.user; + + var limit = ir_options["web_m2x_options.field_limit_entries"]; + if (!_.isUndefined(limit)) { + limit = parseInt(limit, 10); + } + + for (const fieldName in activeFields) { + const field = fields[fieldName]; + if (!isX2Many(field)) { + // What follows only concerns x2many fields + continue; + } + const fieldInfo = activeFields[fieldName]; + if (fieldInfo.modifiers.invisible === true) { + // No need to fetch the sub view if the field is always invisible + continue; + } + + if (!fieldInfo.FieldComponent.useSubView) { + // The FieldComponent used to render the field doesn't need a sub view + continue; + } + + let viewType = fieldInfo.viewMode || "list,kanban"; + viewType = viewType.replace("tree", "list"); + if (viewType.includes(",")) { + viewType = isSmall ? "kanban" : "list"; + } + fieldInfo.viewMode = viewType; + if (fieldInfo.views[viewType] && limit) { + fieldInfo.views[viewType].limit = limit; + } + } + }, +}); diff --git a/addons/web_m2x_options/static/src/components/relational_utils.esm.js b/addons/web_m2x_options/static/src/components/relational_utils.esm.js new file mode 100644 index 0000000..1fbe39e --- /dev/null +++ b/addons/web_m2x_options/static/src/components/relational_utils.esm.js @@ -0,0 +1,221 @@ +/** @odoo-module **/ + +import {Many2XAutocomplete} from "@web/views/fields/relational_utils"; +import {patch} from "@web/core/utils/patch"; +import {sprintf} from "@web/core/utils/strings"; +const {Component} = owl; + +export function is_option_set(option) { + if (_.isUndefined(option)) return false; + if (typeof option === "string") return option === "true" || option === "True"; + if (typeof option === "boolean") return option; + return false; +} + +patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", { + setup() { + this._super(...arguments); + this.ir_options = Component.env.session.web_m2x_options; + }, + + async loadOptionsSource(request) { + if (this.lastProm) { + this.lastProm.abort(false); + } + // Add options limit used to change number of selections record + // returned. + if (!_.isUndefined(this.ir_options["web_m2x_options.limit"])) { + this.props.searchLimit = parseInt( + this.ir_options["web_m2x_options.limit"], + 10 + ); + this.limit = this.props.searchLimit; + } + + if (typeof this.props.nodeOptions.limit === "number") { + this.props.searchLimit = this.props.nodeOptions.limit; + this.limit = this.props.searchLimit; + } + + // Add options field_color and colors to color item(s) depending on field_color value + this.field_color = this.props.nodeOptions.field_color; + this.colors = this.props.nodeOptions.colors; + + this.lastProm = this.orm.call(this.props.resModel, "name_search", [], { + name: request, + operator: "ilike", + args: this.props.getDomain(), + limit: this.props.searchLimit + 1, + context: this.props.context, + }); + const records = await this.lastProm; + + var options = records.map((result) => ({ + value: result[0], + id: result[0], + label: result[1].split("\n")[0], + })); + + // Limit results if there is a custom limit options + if (this.limit) { + options = options.slice(0, this.props.searchLimit); + } + + // Search result value colors + if (this.colors && this.field_color) { + var value_ids = options.map((result) => result.value); + const objects = await this.orm.call( + this.props.resModel, + "search_read", + [], + { + domain: [["id", "in", value_ids]], + fields: [this.field_color], + } + ); + for (var index in objects) { + for (var index_value in options) { + if (options[index_value].id === objects[index].id) { + // Find value in values by comparing ids + var option = options[index_value]; + // Find color with field value as key + var color = + this.colors[objects[index][this.field_color]] || "black"; + option.style = "color:" + color; + break; + } + } + } + } + + // Quick create + // Note: Create should be before `search_more` (reserve native order) + // One more reason: when calling `onInputBlur`, native select the first option (activeSourceOption) + // which triggers m2o_dialog if m2o_dialog=true + var create_enabled = + this.props.quickCreate && !this.props.nodeOptions.no_create; + + var raw_result = _.map(records, function (x) { + return x[1]; + }); + var quick_create = is_option_set(this.props.nodeOptions.create), + quick_create_undef = _.isUndefined(this.props.nodeOptions.create), + m2x_create_undef = _.isUndefined(this.ir_options["web_m2x_options.create"]), + m2x_create = is_option_set(this.ir_options["web_m2x_options.create"]); + var show_create = + (!this.props.nodeOptions && (m2x_create_undef || m2x_create)) || + (this.props.nodeOptions && + (quick_create || + (quick_create_undef && (m2x_create_undef || m2x_create)))); + if ( + create_enabled && + !this.props.nodeOptions.no_quick_create && + request.length > 0 && + !_.contains(raw_result, request) && + show_create + ) { + options.push({ + label: sprintf(this.env._t(`Create "%s"`), request), + classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create", + action: async (params) => { + try { + await this.props.quickCreate(request, params); + } catch { + const context = this.getCreationContext(request); + return this.openMany2X({context}); + } + }, + }); + } + + // Search more... + // Resolution order: + // 1- check if "search_more" is set locally in node's options + // 2- if set locally, apply its value + // 3- if not set locally, check if it's set globally via ir.config_parameter + // 4- if set globally, apply its value + // 5- if not set globally either, check if returned values are more than node's limit + var search_more = false; + if (!_.isUndefined(this.props.nodeOptions.search_more)) { + search_more = is_option_set(this.props.nodeOptions.search_more); + } else if (!_.isUndefined(this.ir_options["web_m2x_options.search_more"])) { + search_more = is_option_set(this.ir_options["web_m2x_options.search_more"]); + } else { + search_more = + !this.props.noSearchMore && this.props.searchLimit < records.length; + } + if (search_more) { + options.push({ + label: this.env._t("Search More..."), + action: this.onSearchMore.bind(this, request), + classList: "o_m2o_dropdown_option o_m2o_dropdown_option_search_more", + }); + } + + // Create and Edit + const canCreateEdit = + "createEdit" in this.activeActions + ? this.activeActions.createEdit + : this.activeActions.create; + if ( + !request.length && + !this.props.value && + (this.props.quickCreate || canCreateEdit) + ) { + options.push({ + label: this.env._t("Start typing..."), + classList: "o_m2o_start_typing", + unselectable: true, + }); + } + + // Create and edit ... + var create_edit = + is_option_set(this.props.nodeOptions.create) || + is_option_set(this.props.nodeOptions.create_edit), + create_edit_undef = + _.isUndefined(this.props.nodeOptions.create) && + _.isUndefined(this.props.nodeOptions.create_edit), + m2x_create_edit_undef = _.isUndefined( + this.ir_options["web_m2x_options.create_edit"] + ), + m2x_create_edit = is_option_set( + this.ir_options["web_m2x_options.create_edit"] + ); + var show_create_edit = + (!this.props.nodeOptions && (m2x_create_edit_undef || m2x_create_edit)) || + (this.props.nodeOptions && + (create_edit || + (create_edit_undef && (m2x_create_edit_undef || m2x_create_edit)))); + if ( + create_enabled && + !this.props.nodeOptions.no_create_edit && + show_create_edit && + request.length && + canCreateEdit + ) { + const context = this.getCreationContext(request); + options.push({ + label: this.env._t("Create and edit..."), + classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create_edit", + action: () => this.openMany2X({context}), + }); + } + + // No records + if (!records.length && !this.activeActions.create) { + options.push({ + label: this.env._t("No records"), + classList: "o_m2o_no_result", + unselectable: true, + }); + } + + return options; + }, +}); + +Many2XAutocomplete.defaultProps = { + ...Many2XAutocomplete.defaultProps, + nodeOptions: {}, +}; diff --git a/addons/web_m2x_options/tests/__init__.py b/addons/web_m2x_options/tests/__init__.py new file mode 100644 index 0000000..b472ff3 --- /dev/null +++ b/addons/web_m2x_options/tests/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2020 initOS GmbH. +from . import test_ir_config_parameter diff --git a/addons/web_m2x_options/tests/test_ir_config_parameter.py b/addons/web_m2x_options/tests/test_ir_config_parameter.py new file mode 100644 index 0000000..eae00c7 --- /dev/null +++ b/addons/web_m2x_options/tests/test_ir_config_parameter.py @@ -0,0 +1,28 @@ +# Copyright 2020 initOS GmbH. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests import common + + +class TestIrConfigParameter(common.TransactionCase): + @classmethod + def setUpClass(cls): + super(TestIrConfigParameter, cls).setUpClass() + cls.env["ir.config_parameter"].set_param("web_m2x_options.limit", 10) + cls.env["ir.config_parameter"].set_param("web_m2x_options.create_edit", "True") + cls.env["ir.config_parameter"].set_param("web_m2x_options.create", "True") + cls.env["ir.config_parameter"].set_param("web_m2x_options.search_more", "False") + cls.env["ir.config_parameter"].set_param("web_m2x_options.m2o_dialog", "True") + + def test_web_m2x_options_key(self): + web_m2x_options = self.env["ir.config_parameter"].get_web_m2x_options() + self.assertIn("web_m2x_options.limit", web_m2x_options) + self.assertNotIn("web_m2x_options.m2o_dialog_test", web_m2x_options) + + def test_web_m2x_options_value(self): + web_m2x_options = self.env["ir.config_parameter"].get_web_m2x_options() + self.assertEqual(web_m2x_options["web_m2x_options.limit"], "10") + self.assertTrue(bool(web_m2x_options["web_m2x_options.create_edit"])) + self.assertTrue(bool(web_m2x_options["web_m2x_options.create"])) + self.assertEqual(web_m2x_options["web_m2x_options.search_more"], "False") + self.assertTrue(bool(web_m2x_options["web_m2x_options.m2o_dialog"])) diff --git a/docker-compose.yml b/docker-compose.yml index 3b7ba85..c1d9a02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,12 +9,14 @@ services: build: context: ./openems-backend image: openems-backend:latest - environment: - DB_HOST: applicationdb.clkigksc2ezs.us-east-1.rds.amazonaws.com # Assumes the PostgreSQL service is named openems-database - DB_PORT: "5432" - DB_NAME: openemsdb - DB_USER: openems - DB_PASSWORD: openemspassword + ports: + - "8075:8075" + # environment: + # DB_HOST: applicationdb.clkigksc2ezs.us-east-1.rds.amazonaws.com # Assumes the PostgreSQL service is named openems-database + # DB_PORT: "5432" + # DB_NAME: openemsdb + # DB_USER: openems + # DB_PASSWORD: openemspassword # depends_on: # - openems-database @@ -22,45 +24,80 @@ services: build: context: ./openems-edge image: openems-edge:latest - - db: - build: - context: ./odoo-database - image: odoo-db - user: root - environment: - - POSTGRES_USER=odoo - - POSTGRES_PASSWORD=odoo16@2022 - - POSTGRES_DB=postgres - restart: always # run as a service - volumes: - - ./postgresql:/var/lib/postgresql/data - + odoo16: build: context: ./odoo - image: odoo - user: root - depends_on: - - db + image: odoo:latest ports: - - "10016:8069" - - "20016:8072" # live chat - tty: true - command: -- + - "8069:8069" environment: - - HOST=db + - HOST=odoodb.clkigksc2ezs.us-east-1.rds.amazonaws.com - USER=odoo - - PASSWORD=odoo16@2022 + - PORT=5432 + - PASSWORD=Icui4cyou # Replace 'odoo-password' with your actual password volumes: - #- /etc/timezone:/etc/timezone:ro - #- /etc/localtime:/etc/localtime:ro - # - ./entrypoint.sh:/entrypoint.sh # if you want to install additional Python packages, uncomment this line! + - odoo-web-data:/var/lib/odoo + - ./config:/etc/odoo - ./addons:/mnt/extra-addons - - ./etc:/etc/odoo - restart: always + restart: always # run as a service + # db: + # build: + # context: ./odoo-database + # image: odoo-db + # user: root + # environment: + # - POSTGRES_DB=postgres + # - POSTGRES_PASSWORD=odoo # Replace 'odoo-password' with your actual password + # - POSTGRES_USER=odoo + # - PGDATA=/var/lib/postgresql/data/pgdata + # volumes: + # - odoo-db-data:/var/lib/postgresql/data/pgdata + # restart: always # run as a service +volumes: + odoo-web-data: + odoo-db-data: + + # db: + # build: + # context: ./odoo-database + # image: odoo-db + # user: root + # environment: + # - POSTGRES_USER=odoo + # - POSTGRES_PASSWORD=odoo16@2022 + # - POSTGRES_DB=postgres + # restart: always # run as a service + # volumes: + # - ./postgresql:/var/lib/postgresql/data + + # odoo16: + # build: + # context: ./odoo + # image: odoo + # user: root + # depends_on: + # - db + # ports: + # - "10016:8069" + # - "20016:8072" # live chat + # tty: true + # command: -- + # environment: + # - HOST=db + # - USER=odoo + # - PASSWORD=odoo16@2022 + # volumes: + # #- /etc/timezone:/etc/timezone:ro + # #- /etc/localtime:/etc/localtime:ro + # # - ./entrypoint.sh:/entrypoint.sh # if you want to install additional Python packages, uncomment this line! + # - ./addons:/mnt/extra-addons + # - ./etc:/etc/odoo + # restart: always + + # openems-database: # build: diff --git a/etc/odoo.conf b/etc/odoo.conf deleted file mode 100644 index f7b2fc6..0000000 --- a/etc/odoo.conf +++ /dev/null @@ -1,400 +0,0 @@ -[options] -; =================== -; | Common options) | -; =================== -; ------ -; -c / --config | specify alternate config file -; ------ -; config = - -; ------ -; -s / --save | save configuration to ~/.odoorc (or to ~/.openerp_serverrc if it exists) -; ------ -; save = - -; ------ -; -i / --init | install one or more modules (comma-separated list, use "all" for all modules), requires -d -; ------ -; init = - -; ------ -; -u / --update | update one or more modules (comma-separated list, use "all" for all modules). Requires -d. -; ------ -; update = - -; ------ -; --without-demo | disable loading demo data for modules to be installed (comma-separated, use "all" for all modules). Requires -d and -i. Default is %default -; ------ -; without_demo = - -; ------ -; -P / --import-partial | Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states. -; ------ -; import_partial = - -; ------ -; --pidfile | file where the server pid will be stored -; ------ -; pidfile = - -; ------ -; --addons-path | type = string | specify additional addons paths (separated by commas). -; ------ -addons_path = /mnt/extra-addons - -; ------ -; --upgrade-path | type = string | specify an additional upgrade path. -; ------ -; upgrade_path = - -; ------ -; --load | Comma-separated list of server-wide modules. -; ------ -; server_wide_modules = base,web - -; ------ -; -D / --data-dir | Directory where to store Odoo data -; ------ -data_dir = /etc/odoo - -admin_passwd = openemspassword - -; ============================== -; | HTTP Service Configuration | -; ============================== -; ------ -; --http-interface | Listen interface address for HTTP services. Keep empty to listen on all interfaces (0.0.0.0) -; ------ -; http_interface = - -; ------ -; -p / --http-port | type = int | Listen port for the main HTTP service -; ------ -; http_port = 8069 - -; ------ -; --longpolling-port | type = int | Listen port for the longpolling HTTP service -; ------ -; longpolling_port = 8072 - -; ------ -; --no-http | Disable the HTTP and Longpolling services entirely -; ------ -; http_enable = True - -; ------ -; --proxy-mode | Activate reverse proxy WSGI wrappers (headers rewriting) Only enable this when running behind a trusted web proxy! -; ------ -; proxy_mode = - -; ------ -; --xmlrpc-interface | SUPPRESSHELP -; ------ -; http_interface = - -; ------ -; --xmlrpc-port | type = int | SUPPRESSHELP -; ------ -; http_port = - -; ------ -; --no-xmlrpc | SUPPRESSHELP -; ------ -; http_enable = - -; =============================== -; | Web interface Configuration | -; =============================== -; ------ -; --db-filter | Regular expressions for filtering available databases for Web UI. The expression can use %d (domain) and %h (host) placeholders. -; ------ -; dbfilter = - -; ========================= -; | Testing Configuration | -; ========================= -; ------ -; --test-file | Launch a python test file. -; ------ -; test_file = - -; ------ -; --test-enable | Enable unit tests. -; ------ -; test_enable = - -; ------ -; --test-tags | Comma-separated list of spec to filter which tests to execute. Enable unit tests if set. A filter spec has the format: [-][tag][/module][:class][.method] The '-' specifies if we want to include or exclude tests matching this spec. The tag will match tags added on a class with a @tagged decorator. By default tag value is 'standard' when not given on include mode. '*' will match all tags. Tag will also match module name (deprecated, use /module) The module, class, and method will respectively match the module name, test class name and test method name. examples: :TestClass.test_func,/test_module,external -; ------ -; test_tags = - -; ------ -; --screencasts | Screencasts will go in DIR/{db_name}/screencasts. -; ------ -; screencasts = - -; ------ -; --screenshots | Screenshots will go in DIR/{db_name}/screenshots. Defaults to /etc/odoo/odoo_tests. -; ------ -; screenshots = /etc/odoo/odoo_tests - -; ========================= -; | Logging Configuration | -; ========================= -; ------ -; --logfile | file where the server log will be stored -; ------ -logfile = /etc/odoo/odoo-server.log - -; ------ -; --syslog | Send the log to the syslog server -; ------ -; syslog = - -; ------ -; --log-handler | setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "odoo.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO") -; ------ -; None = :INFO - -; ------ -; --log-request | shortcut for --log-handler=odoo.http.rpc.request:DEBUG -; ------ -; log_handler = - -; ------ -; --log-response | shortcut for --log-handler=odoo.http.rpc.response:DEBUG -; ------ -; log_handler = - -; ------ -; --log-web | shortcut for --log-handler=odoo.http:DEBUG -; ------ -; log_handler = - -; ------ -; --log-sql | shortcut for --log-handler=odoo.sql_db:DEBUG -; ------ -; log_handler = - -; ------ -; --log-db | Logging database -; ------ -; log_db = - -; ------ -; --log-db-level | Logging database level -; ------ -; log_db_level = warning - -; ------ -; --log-level | type = choice | choices = ['info', 'debug_rpc', 'warn', 'test', 'critical', 'runbot', 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'] | specify the level of the logging. Accepted values: ['info', 'debug_rpc', 'warn', 'test', 'critical', 'runbot', 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset']. -; ------ -; log_level = info - -; ====================== -; | SMTP Configuration | -; ====================== -; ------ -; --email-from | specify the SMTP email address for sending email -; ------ -; email_from = - -; ------ -; --smtp | specify the SMTP server for sending email -; ------ -; smtp_server = localhost - -; ------ -; --smtp-port | type = int | specify the SMTP port -; ------ -; smtp_port = 25 - -; ------ -; --smtp-ssl | if passed, SMTP connections will be encrypted with SSL (STARTTLS) -; ------ -; smtp_ssl = - -; ------ -; --smtp-user | specify the SMTP username for sending email -; ------ -; smtp_user = - -; ------ -; --smtp-password | specify the SMTP password for sending email -; ------ -; smtp_password = - -; ============================ -; | Database related options | -; ============================ -; ------ -; -d / --database | specify the database name -; ------ -; db_name = - -; ------ -; -r / --db_user | specify the database user name -; ------ -; db_user = - -; ------ -; -w / --db_password | specify the database password -; ------ -; db_password = - -; ------ -; --pg_path | specify the pg executable path -; ------ -; pg_path = - -; ------ -; --db_host | specify the database host -; ------ -; db_host = - -; ------ -; --db_port | type = int | specify the database port -; ------ -; db_port = - -; ------ -; --db_sslmode | type = choice | choices = ['disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full'] | specify the database ssl connection mode (see PostgreSQL documentation) -; ------ -; db_sslmode = prefer - -; ------ -; --db_maxconn | type = int | specify the maximum number of physical connections to PostgreSQL -; ------ -; db_maxconn = 64 - -; ------ -; --db-template | specify a custom database template to create a new database -; ------ -; db_template = template0 - -; ======================== -; | Internationalisation | -; ======================== -; ------ -; --load-language | specifies the languages for the translations you want to be loaded -; ------ -; load_language = - -; ------ -; -l / --language | specify the language of the translation file. Use it with --i18n-export or --i18n-import -; ------ -; language = - -; ------ -; --i18n-export | export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit -; ------ -; translate_out = - -; ------ -; --i18n-import | import a CSV or a PO file with translations and exit. The '-l' option is required. -; ------ -; translate_in = - -; ------ -; --i18n-overwrite | overwrites existing translation terms on updating a module or importing a CSV or a PO file. -; ------ -; overwrite_existing_translations = - -; ------ -; --modules | specify modules to export. Use in combination with --i18n-export -; ------ -; translate_modules = - -; ============================ -; | Security-related options | -; ============================ -; ------ -; --no-database-list | Disable the ability to obtain or view the list of databases. Also disable access to the database manager and selector, so be sure to set a proper --database parameter first -; ------ -; list_db = True - -; ==================== -; | Advanced options | -; ==================== -; ------ -; --dev | type = string | Enable developer mode. Param: List of options separated by comma. Options : all, [pudb|wdb|ipdb|pdb], reload, qweb, werkzeug, xml -; ------ -dev_mode = reload - -; ------ -; --shell-interface | type = string | Specify a preferred REPL to use in shell mode. Supported REPLs are: [ipython|ptpython|bpython|python] -; ------ -; shell_interface = - -; ------ -; --stop-after-init | stop the server after its initialization -; ------ -; stop_after_init = - -; ------ -; --osv-memory-count-limit | type = int | Force a limit on the maximum number of records kept in the virtual osv_memory tables. The default is False, which means no count-based limit. -; ------ -; osv_memory_count_limit = - -; ------ -; --transient-age-limit | type = float | Time limit (decimal value in hours) records created with a TransientModel (mosly wizard) are kept in the database. Default to 1 hour. -; ------ -; transient_age_limit = 1.0 - -; ------ -; --osv-memory-age-limit | type = float | Deprecated alias to the transient-age-limit option -; ------ -; osv_memory_age_limit = - -; ------ -; --max-cron-threads | type = int | Maximum number of threads processing concurrently cron jobs (default 2). -; ------ -; max_cron_threads = 2 - -; ------ -; --unaccent | Try to enable the unaccent extension when creating new databases. -; ------ -; unaccent = - -; ------ -; --geoip-db | Absolute path to the GeoIP database file. -; ------ -; geoip_database = /usr/share/GeoIP/GeoLite2-City.mmdb - -; =========================== -; | Multiprocessing options | -; =========================== -; ------ -; --workers | type = int | Specify the number of workers, 0 disable prefork mode. -; ------ -; workers = - -; ------ -; --limit-memory-soft | type = int | Maximum allowed virtual memory per worker (in bytes), when reached the worker be reset after the current request (default 2048MiB). -; ------ -; limit_memory_soft = 2147483648 - -; ------ -; --limit-memory-hard | type = int | Maximum allowed virtual memory per worker (in bytes), when reached, any memory allocation will fail (default 2560MiB). -; ------ -; limit_memory_hard = 2684354560 - -; ------ -; --limit-time-cpu | type = int | Maximum allowed CPU time per request (default 60). -; ------ -; limit_time_cpu = 60 - -; ------ -; --limit-time-real | type = int | Maximum allowed Real time per request (default 120). -; ------ -; limit_time_real = 120 - -; ------ -; --limit-time-real-cron | type = int | Maximum allowed Real time per cron job. (default: --limit-time-real). Set to 0 for no limit. -; ------ -; limit_time_real_cron = -1 - -; ------ -; --limit-request | type = int | Maximum number of request to be processed per worker (default 8192). -; ------ -; limit_request = 8192 diff --git a/etc/requirements.txt b/etc/requirements.txt deleted file mode 100644 index 6853d72..0000000 --- a/etc/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# ----------------------- -# | Add Python packages | -# ----------------------- -# To install below packages at startup, uncomment this line in "docker-compose.yml" file! -# - ./entrypoint.sh:/entrypoint.sh -# then down the docker container ($ docker-compose down) and up it again ($ docker-compose up -d). -# ----------------------- -# paramiko==2.7.2 # for auto_backup module diff --git a/iac/backend.tf b/iac/backend.tf index 9f3fdd3..1ba5fd6 100644 --- a/iac/backend.tf +++ b/iac/backend.tf @@ -1,8 +1,8 @@ terraform { backend "s3" { - bucket = "openems-deployment-tf-state-file" + bucket = "openems-tf-state-file" key = "openems-app/terraform.tfstate" region = "us-east-1" - dynamodb_table = "terraform-state-lock-openems-deployment" + dynamodb_table = "terraform-state-lock-openems" } } diff --git a/iac/ecs.tf b/iac/ecs.tf index acc0c94..40fcb10 100644 --- a/iac/ecs.tf +++ b/iac/ecs.tf @@ -59,29 +59,6 @@ resource "aws_ecs_task_definition" "ecs_task_definition" { image = "${local.secrets.ecr_registry}/${var.image_name_openems_backend}:${var.image_tag}" essential = false - environment = [ - { - name = "DB_HOST", - value = "applicationdb.clkigksc2ezs.us-east-1.rds.amazonaws.com" - }, - { - name = "DB_PORT", - value = "5432" - }, - { - name = "DB_NAME", - value = "openemsdb" - }, - { - name = "DB_USER", - value = "openems" - }, - { - name = "DB_PASSWORD", - value = "openempassword" - } - ] - portMappings = [ { containerPort = 8075 @@ -117,17 +94,14 @@ resource "aws_ecs_task_definition" "ecs_task_definition" { { name = "${var.project_name}-${var.environment}-container-odoo" image = "${local.secrets.ecr_registry}/${var.image_name_odoo}:${var.image_tag}" - essential = false + essential = true environment = [ { name = "HOST", - value = "db" + value = "odoodb.clkigksc2ezs.us-east-1.rds.amazonaws.com" }, - # { - # name = "DB_PORT", - # value = "5432" - # }, + # { # name = "DB_NAME", # value = "openemsdb" @@ -136,12 +110,20 @@ resource "aws_ecs_task_definition" "ecs_task_definition" { name = "USER", value = "odoo" }, + + { + name = "PORT", + value = "5432" + }, + { name = "PASSWORD", - value = "odoo16@2022" + value = "Icui4cyou" } ] + # entrypoint = ["/entrypoint.sh"] + portMappings = [ { containerPort = 8069 @@ -149,42 +131,6 @@ resource "aws_ecs_task_definition" "ecs_task_definition" { } ] - logConfiguration = { - logDriver = "awslogs", - options = { - "awslogs-group" = "${aws_cloudwatch_log_group.log_group.name}", - "awslogs-region" = "${var.region}", - "awslogs-stream-prefix" = "ecs" - } - } - }, - { - name = "${var.project_name}-${var.environment}-container-odoo-db" - image = "${local.secrets.ecr_registry}/${var.image_name_odoo_db}:${var.image_tag}" - essential = false - environment = [ - { - name = "POSTGRES_USER", - value = "odoo" - }, - { - name = "POSTGRES_PASSWORD", - value = "odoo16@2022 " - }, - { - name = "POSTGRES_DB", - value = "postgres" - } - ] - - portMappings = [ - { - containerPort = 5433 - hostPort = 5433 - } - ] - - logConfiguration = { logDriver = "awslogs", options = { diff --git a/iac/security-group.tf b/iac/security-group.tf index c777a55..4b60975 100644 --- a/iac/security-group.tf +++ b/iac/security-group.tf @@ -1,37 +1,45 @@ # create security group for the application load balancer resource "aws_security_group" "openems_security_group" { name = "${var.project_name}-${var.environment}-openems-sg" - description = "enable LL access on port 80/443" + description = "enable access on ports" vpc_id = aws_vpc.vpc.id ingress { description = "all traffic" - from_port = 0 - to_port = 0 + from_port = 8082 + to_port = 8082 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "all traffic" - from_port = 8075 - to_port = 8075 + from_port = 8069 + to_port = 8069 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "all traffic" - from_port = 8069 - to_port = 8069 + from_port = 8086 + to_port = 8086 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "all traffic" - from_port = 8086 - to_port = 8086 + from_port = 8089 + to_port = 8089 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "all traffic" + from_port = 8080 + to_port = 8080 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } @@ -44,6 +52,22 @@ resource "aws_security_group" "openems_security_group" { cidr_blocks = ["0.0.0.0/0"] } + ingress { + description = "all traffic" + from_port = 8075 + to_port = 8075 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + +ingress { + description = "all traffic" + from_port = 8079 + to_port = 8079 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + egress { from_port = 0 to_port = 0 @@ -59,13 +83,13 @@ resource "aws_security_group" "openems_security_group" { # create security group for the database resource "aws_security_group" "database_security_group" { name = "${var.project_name}-${var.environment}-database-sg" - description = "enable MySQL/Aurora access on port 3306 via app server sg" + description = "enable Postgres access on port 5432 via openems server sg" vpc_id = aws_vpc.vpc.id ingress { - description = "mysql/aurora access" - from_port = 3306 - to_port = 3306 + description = "postgres/aurora access" + from_port = 5432 + to_port = 5432 protocol = "tcp" security_groups = [aws_security_group.openems_security_group.id] } diff --git a/iac/terraform.tfvars b/iac/terraform.tfvars index d594e64..d08f86b 100644 --- a/iac/terraform.tfvars +++ b/iac/terraform.tfvars @@ -17,7 +17,7 @@ image_name_openems_backend = "openems-backend" image_name_openems_edge = "openems-edge" # image_name_openems_db = "openems-db" image_name_odoo = "odoo" -image_name_odoo_db = "odoo-db" +# image_name_odoo_db = "odoo-db" image_tag = "latest" @@ -28,10 +28,10 @@ secrets_manager_secret_name = "openems-demo-secret" # rds variables engine_type="postgres" -engine_type_version="16.2" +engine_type_version="18.1" multi_az_deployment="false" -database_cluster_name="applicationdb" -master_username="openems" -master_password="openemspassword" -initial_database_name="openemsdb" -instance_class_type="db.t3.micro" \ No newline at end of file +database_cluster_name="odoodb" +master_username="odoo" +master_password="Icui4cyou" +initial_database_name="odoodb" +instance_class_type="db.t3.micro" diff --git a/iac/variables.tf b/iac/variables.tf index d14b672..6ba6e1b 100644 --- a/iac/variables.tf +++ b/iac/variables.tf @@ -67,10 +67,10 @@ variable "image_name_odoo" { type = string } -variable "image_name_odoo_db" { - description = "the docker image name" - type = string -} +# variable "image_name_odoo_db" { +# description = "the docker image name" +# type = string +# } variable "image_name_openems_edge" { description = "the docker image name" diff --git a/odoo/Dockerfile b/odoo/Dockerfile index 23bcdbd..c4bda0d 100644 --- a/odoo/Dockerfile +++ b/odoo/Dockerfile @@ -2,6 +2,12 @@ FROM odoo:16.0 +EXPOSE 8069 -# Expose port 8069 for Odoo web interface -EXPOSE 8069 \ No newline at end of file + +# COPY entrypoint.sh /entrypoint.sh +# RUN chmod +x /entrypoint.sh + + +# ENTRYPOINT ["/entrypoint.sh"] +# CMD ["odoo"] \ No newline at end of file diff --git a/entrypoint.sh b/odoo/entrypoint.sh similarity index 80% rename from entrypoint.sh rename to odoo/entrypoint.sh index bde63c8..c2d755f 100644 --- a/entrypoint.sh +++ b/odoo/entrypoint.sh @@ -2,18 +2,16 @@ set -e +if [ -v PASSWORD_FILE ]; then + PASSWORD="$(< $PASSWORD_FILE)" +fi + # set the postgres database host, port, user and password according to the environment # and pass them as arguments to the odoo process if not present in the config file -: ${HOST:=${DB_PORT_5432_TCP_ADDR:='db'}} +: ${HOST:=${DB_PORT_5432_TCP_ADDR:='odoodb.clkigksc2ezs.us-east-1.rds.amazonaws.com'}} : ${PORT:=${DB_PORT_5432_TCP_PORT:=5432}} : ${USER:=${DB_ENV_POSTGRES_USER:=${POSTGRES_USER:='odoo'}}} -: ${PASSWORD:=${DB_ENV_POSTGRES_PASSWORD:=${POSTGRES_PASSWORD:='odoo16@2022'}}} - -# install python packages -pip3 install pip --upgrade -pip3 install -r /etc/odoo/requirements.txt - -# sed -i 's|raise werkzeug.exceptions.BadRequest(msg)|self.jsonrequest = {}|g' /usr/lib/python3/dist-packages/odoo/http.py +: ${PASSWORD:=${DB_ENV_POSTGRES_PASSWORD:=${POSTGRES_PASSWORD:='Icui4cyou'}}} DB_ARGS=() function check_config() { @@ -48,4 +46,4 @@ case "$1" in exec "$@" esac -exit 1 \ No newline at end of file +exit 1 diff --git a/openems-edge/Dockerfile b/openems-edge/Dockerfile index 3f341ed..b4086f6 100644 --- a/openems-edge/Dockerfile +++ b/openems-edge/Dockerfile @@ -1,5 +1,5 @@ # Use an official Java runtime as a parent image -FROM openjdk:17 +FROM eclipse-temurin:17 # Set the working directory in the container WORKDIR /usr/lib/openems diff --git a/run.sh b/run.sh deleted file mode 100644 index 47ccd36..0000000 --- a/run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -DESTINATION=$1 -PORT=$2 -CHAT=$3 -# clone Odoo directory -git clone --depth=1 https://github.com/minhng92/odoo-16-docker-compose $DESTINATION -rm -rf $DESTINATION/.git -# set permission -mkdir -p $DESTINATION/postgresql -sudo chmod -R 777 $DESTINATION -# config -if grep -qF "fs.inotify.max_user_watches" /etc/sysctl.conf; then echo $(grep -F "fs.inotify.max_user_watches" /etc/sysctl.conf); else echo "fs.inotify.max_user_watches = 524288" | sudo tee -a /etc/sysctl.conf; fi -sudo sysctl -p -sed -i 's/10016/'$PORT'/g' $DESTINATION/docker-compose.yml -sed -i 's/20016/'$CHAT'/g' $DESTINATION/docker-compose.yml -# run Odoo -docker-compose -f $DESTINATION/docker-compose.yml up -d - -echo 'Started Odoo @ http://localhost:'$PORT' | Master Password: minhng.info | Live chat port: '$CHAT