Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[flake8]
max-line-length = 88
extend-select = B950
extend-ignore = E203, E501, E701
per-file-ignores =
__manifest__.py:B018
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: test

on:
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:
pre-commit:
runs-on: ubuntu-22.04
steps:
- name: Checkout branch
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Run pre-commit hooks
uses: pre-commit/action@v3.0.1
30 changes: 30 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: mixed-line-ending
- id: end-of-file-fixer
- id: detect-private-key
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/crate-ci/typos.git
rev: v1.21.0
hooks:
- id: typos
- repo: https://github.com/psf/black.git
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8.git
rev: 7.0.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear==24.4.26
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
- id: yamllint
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# OpenStack Integration Addon for Odoo v13

This is an addon to facilitate the integration between OpenStack using Adjutant and Distil with Odoo v13.

These are models and changes based on the Catalystcloud use cases, and based on how we invoice, but likely applicable in some form for other clouds running OpenStack.

## Altered Odoo Models

### Partner

We add quite a few custom fields here that link back to our models elsewhere. We also add the field `stripe_customer_id` to optional link to a customer in Stripe itself for credit card related things.

### Sale Order

The main change to Sale Order is the inclusion of `os_project` on both the Sale Order itself, and the lines. The idea here being that `os_project` on the Sale Order represents the root project for the given project group which the invoice is ultimately for, while `os_project` on each line allows each line to potentially come from a different project in that subgroup in the case of invoice grouping. We also add overrides for `_prepare_invoice` and `_prepare_invoice_line` to handle these fields.

**NOTE**: We also add `os_invoice_date` to deal with a particularly annoying issue around sale order date and how it is handled. But this will be moved to a standalone addon and renamed to `invoice_date` later.

### Account Move

Much like Sale Order, we add `os_project` to both the Account Move (invoice) and Account Move Lines. This is for the same reasons/functionality as in Sale Order.

We also have the function `is_openstack_invoice`, which works off is `os_project` is set or not.

Then there is a custom categorisation function `categorised_openstack_invoice_lines` which sorts things in a way more useful for the PDF summary page.

## Custom Models

The bulk of this addon is custom addons, many of which don't have much logic built in on the odoo side, and most of that logic (for now) is primarily handled external to Odoo in Adjutant and Distil (including the Distil Odoo Writer).

### Project

The core of this addon is built mostly around the OpenStack concept of a project, and this model is meant to represent that Odoo side. A project can only have one `owner` which is the customer partner, but then through an intermediary model called `project_contacts` has relationships to other partners such as billing contacts, etc.

Many other models link back to projects, as they are ultimately the main connection between a customer, and their OpenStack side usage.

### Customer Group

A customer group is a grouping of customers for the purposes of special discounting. The customer group is mostly for special case handling of volume discounts.

While not implemented yet, a group can also be given a pricelist which would then apply to all customers in the group.

### Voucher Code (Not used yet)

These are unused as yet, but their intended function is to allow us to create codes that can be added to an account during signup, or later, which will confer credit, or discounts as tired to the code. They would then turn into a Credit, Grant, or add a customer to a customer group. Their purpose was to be useful for a range of things.

### Credit

Credits are a model representation of credit that an OpenStack Customer has against their invoices. The balance is made up from credit transactions that are attached to the model, which gives us a history of the use of that credit.

Credits by default are not refundable, but for the purposes of prepaid or SLA credit they may have to be, and the credit_type model lets you define if a given type of credit has to be able to be refunded.

### Grant

A grant is like credit, except unlike credits, it doesn't have a balance, just a max value that can be applied to an invoice each month. It is reoccurring credit essentially.

A grant type has a special case field `only_on_group_root` that handles a case where this grant cannot be applied to any project that isn't the root of a invoice project group. This is because when grouping invoices credits/grants are applied to the whole group, and something like a dev grant should never be applied to parent projects, so a dev grant can only affect the project it is applied to or sub projects.

### Volume Discount

These are ranged of volume discount, and they are used as part of invoicing. A customer group may have it's own unique volume discounts, but by default a customer without a customer group will use the volume discounts with a group set.

### Term Discount

A model representing an agreed to term discount.

### Support Subscription (Not used, yet)

A model (and type) to represent a support subscription that a customer has signed up for of a given type.

### Reseller

We have a model for representing a reseller on our cloud, as well has what tier that reseller is at. This handles their discounts, as well as other special cases. A project is optionally linked back to a reseller.

### Trial (Not used)

A model representing a trial signup for OpenStack, and the status of that.

### Referral (Not used)

A basic referral model that would allow customers who provide referrals to earn some credit themselves.
20 changes: 20 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (C) 2021-2024 Catalyst Cloud Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from . import controllers
from . import models
from . import report

__all__ = ["controllers", "models", "report"]
55 changes: 55 additions & 0 deletions __manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (C) 2021-2024 Catalyst Cloud Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

{
"name": "OpenStack Integration",
"category": "Tools",
"summary": """OpenStack integration for the Odoo ERP.""",
"version": "14.0.1.0.0",
"author": "Catalyst Cloud",
"website": "https://catalystcloud.nz",
"license": "Other OSI approved licence",
"depends": [
"account",
"base",
"product",
"report_csv",
"sale",
],
"data": [
"report/openstack_invoice_csv.xml",
"security/openstack_security.xml",
"security/ir.model.access.csv",
"views/main_menu.xml",
"views/account_move_view.xml",
"views/credit_view.xml",
"views/customer_group_view.xml",
"views/grant_view.xml",
"views/project_view.xml",
"views/referral_view.xml",
"views/report_invoice.xml",
"views/reseller_view.xml",
"views/res_partner_view.xml",
"views/sale_order_view.xml",
"views/support_subscription_view.xml",
"views/term_discount_view.xml",
"views/trial_view.xml",
"views/volume_discount_view.xml",
"views/voucher_code_view.xml",
# load mail template after report_invoice.xml which it refers to
"data/mail_template_data.xml",
],
"installable": True,
}
2 changes: 2 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default.extend-words]
datas = "datas"
18 changes: 18 additions & 0 deletions controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (C) 2021-2024 Catalyst Cloud Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from . import main

__all__ = ["main"]
71 changes: 71 additions & 0 deletions controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (C) 2021-2024 Catalyst Cloud Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json

from odoo.http import content_disposition, request, route
from odoo.tools.safe_eval import safe_eval, time

from odoo.addons.report_csv.controllers import main as report


class ReportController(report.ReportController):
"""report_csv.ReportController that fixes a mysterious permission problem"""

@route()
def report_routes(self, reportname, docids=None, converter=None, **data):
if converter == "csv":
report = request.env["ir.actions.report"]._get_report_from_name(reportname)
context = dict(request.env.context)
if docids:
docids = [int(i) for i in docids.split(",")]
if data.get("options"):
data.update(json.loads(data.pop("options")))
if data.get("context"):
# Ignore 'lang' here, because the context in data is the one
# from the webclient *but* if the user explicitly wants to
# change the lang, this mechanism overwrites it.
data["context"] = json.loads(data["context"])
if data["context"].get("lang"):
del data["context"]["lang"]
context.update(data["context"])
# TODO: Investigate why we need to insert sudo() here
csv = report.sudo().with_context(context)._render_csv(docids, data=data)[0]
filename = "{}.{}".format(report.name, "csv")
if docids:
obj = request.env[report.model].browse(docids)
if report.print_report_name and not len(obj) > 1:
report_name = safe_eval(
report.print_report_name,
{"object": obj, "time": time, "multi": False},
)
filename = "{}.{}".format(report_name, "csv")
# When we print multiple records we still allow a custom
# filename.
elif report.print_report_name and len(obj) > 1:
report_name = safe_eval(
report.print_report_name,
{"objects": obj, "time": time, "multi": True},
)
filename = "{}.{}".format(report_name, "csv")
csvhttpheaders = [
("Content-Type", "text/csv"),
("Content-Length", len(csv)),
("Content-Disposition", content_disposition(filename)),
]
return request.make_response(csv, headers=csvhttpheaders)
return super(ReportController, self).report_routes(
reportname, docids, converter, **data
)
Loading