diff --git a/l10n_jp_summary_invoice_carryover/README.rst b/l10n_jp_summary_invoice_carryover/README.rst new file mode 100644 index 0000000..b8a7ab1 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/README.rst @@ -0,0 +1,138 @@ +================================= +Japan Summary Invoice - Carryover +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:022ccd1b135e2643a1528cf22d51219ed2641e159300358c1e27fa5dd8b3a2f0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--japan-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-japan/tree/18.0/l10n_jp_summary_invoice_carryover + :alt: OCA/l10n-japan +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-japan-18-0/l10n-japan-18-0-l10n_jp_summary_invoice_carryover + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-japan&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends Japan Summary Invoice to support carryover amount +tracking for recurring billing cycles. + +It adds the following fields to billings: + +- **Previous Billed Amount**: Total from the previous billing period +- **Payment Amount**: Payments received against the previous billing +- **Carryover Amount**: Outstanding balance carried forward +- **Total Billed Amount**: Carryover plus current purchases + +Manual override fields are available to adjust values when needed. + +The visibility of carryover amounts in the report can be controlled at +company, partner, and billing levels. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +The visibility of carryover amounts in the report can be controlled at +three levels: + +1. **Company** (default for all partners): Go to *Settings > Invoicing > + Japan Summary Invoice* and enable/disable **Show Carryover Amounts**. + +2. **Partner** (override for specific customers): Go to the partner + form, under *Sales & Purchases > Summary Invoice*, set **Show + Carryover Amounts** to: + + - *Use Company Default*: Follow the company setting + - *Yes*: Always show carryover amounts + - *No*: Never show carryover amounts + +3. **Billing** (per-billing override): In the billing form under the + *Carryover* tab, the **Show Carryover Amounts** checkbox is computed + from the partner setting but can be manually adjusted. + +Usage +===== + +1. When creating a new billing, the system automatically finds the most + recent validated billing for the same partner and calculates: + + - **Previous Billed Amount**: Total amount from the previous + billing. + - **Payment Amount**: Payments received on the previous billing + invoices. + - **Carryover Amount**: Previous billed amount minus payments. + +2. Use the **Manual Adj.** toggle to manually override the computed + values if needed. This is useful when the previous billing was + created before this module was installed or when adjustments are + required. +3. When the billing is validated, the current computed values are + automatically frozen by enabling the manual override toggles. This + ensures that subsequent payments on previous invoices do not affect + the validated billing's carryover amounts. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Quartile + +Contributors +------------ + +- `Quartile `__: + + - Yoshi Tashiro + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/l10n-japan `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_jp_summary_invoice_carryover/__init__.py b/l10n_jp_summary_invoice_carryover/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_jp_summary_invoice_carryover/__manifest__.py b/l10n_jp_summary_invoice_carryover/__manifest__.py new file mode 100644 index 0000000..33f0373 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Japan Summary Invoice - Carryover", + "summary": "Add carryover amount tracking to summary invoices", + "version": "18.0.1.0.0", + "category": "Japanese Localization", + "author": "Quartile, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-japan", + "license": "AGPL-3", + "depends": ["l10n_jp_summary_invoice"], + "data": [ + "reports/report_summary_invoice_templates.xml", + "views/account_billing_views.xml", + "views/res_config_settings_views.xml", + "views/res_partner_views.xml", + ], + "development_status": "Alpha", + "installable": True, +} diff --git a/l10n_jp_summary_invoice_carryover/i18n/ja.po b/l10n_jp_summary_invoice_carryover/i18n/ja.po new file mode 100644 index 0000000..46ccea3 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/i18n/ja.po @@ -0,0 +1,219 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_jp_summary_invoice_carryover +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-30 16:40+0000\n" +"PO-Revision-Date: 2025-12-30 16:40+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: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.view_account_billing_form_carryover +msgid "Manual Adj." +msgstr "手入力" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.report_summary_invoice_document_carryover +msgid "Total Billed Amt" +msgstr "今回請求額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model,name:l10n_jp_summary_invoice_carryover.model_account_billing +msgid "Account Billing" +msgstr "合計請求書" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.view_account_billing_form_carryover +msgid "Carryover" +msgstr "繰越" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__carryover_amount +msgid "Carryover Amount" +msgstr "繰越額" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.report_summary_invoice_document_carryover +msgid "Carryover Amt" +msgstr "繰越額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__total_billed_amount +msgid "Carryover plus current billed amount." +msgstr "繰越額と今回取引額の合計。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model,name:l10n_jp_summary_invoice_carryover.model_res_company +msgid "Companies" +msgstr "会社" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model,name:l10n_jp_summary_invoice_carryover.model_res_config_settings +msgid "Config Settings" +msgstr "コンフィグ設定" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model,name:l10n_jp_summary_invoice_carryover.model_res_partner +msgid "Contact" +msgstr "連絡先" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.view_account_billing_form_carryover +msgid "Current Purchase Amount" +msgstr "今回取引額" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.report_summary_invoice_document_carryover +msgid "Current Purchase Amt" +msgstr "今回取引額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_res_company__show_carryover_amounts +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_res_config_settings__show_carryover_amounts +msgid "" +"If enabled, carryover amount fields will be displayed in the summary invoice" +" report." +msgstr "有効にすると、繰越額情報が合計請求書レポートに表示されます。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__payment_amount_manual +msgid "Manual override for payment amount." +msgstr "入金額の手入力値。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__prev_billed_amount_manual +msgid "Manual override for previous billed amount." +msgstr "前回請求額の手入力値。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields.selection,name:l10n_jp_summary_invoice_carryover.selection__res_partner__show_carryover_amounts__no +msgid "No" +msgstr "いいえ" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__payment_amount +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.view_account_billing_form_carryover +msgid "Payment Amount" +msgstr "入金額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__payment_amount_manual +msgid "Payment Amount (Manual)" +msgstr "入金額(手入力)" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.report_summary_invoice_document_carryover +msgid "Payment Amt" +msgstr "入金額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__payment_amount +msgid "Payments received on previous billing invoices." +msgstr "前回請求書に対する入金額。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__prev_billed_amount +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.view_account_billing_form_carryover +msgid "Previous Billed Amount" +msgstr "前回請求額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__prev_billed_amount_manual +msgid "Previous Billed Amount (Manual)" +msgstr "前回請求額(手入力)" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.report_summary_invoice_document_carryover +msgid "Previous Billed Amt" +msgstr "前回請求額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__prev_billing_id +msgid "Previous Billing" +msgstr "前回合計請求" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__prev_billing_candidate_ids +msgid "Previous Billing Candidates" +msgstr "前回合計請求候補" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__carryover_amount +msgid "Previous billing amount minus payments." +msgstr "前回請求額から入金額を差し引いた金額。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__show_carryover_amounts +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_res_company__show_carryover_amounts +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_res_config_settings__show_carryover_amounts +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_res_partner__show_carryover_amounts +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_res_users__show_carryover_amounts +msgid "Show Carryover Amounts" +msgstr "繰越額を表示" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.view_partner_form_carryover +msgid "Summary Invoice" +msgstr "合計請求書" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.res_config_settings_view_form +msgid "" +"The carryover amounts will appear in the summary invoice report if selected" +msgstr "選択すると、繰越額が合計請求書レポートに表示されます" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__total_billed_amount +msgid "Total Billed Amount" +msgstr "今回請求額" + +#. module: l10n_jp_summary_invoice_carryover +#: model_terms:ir.ui.view,arch_db:l10n_jp_summary_invoice_carryover.report_summary_invoice_document_carryover +msgid "Total Billed Amt" +msgstr "今回請求額" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__prev_billed_amount +msgid "Total billed amount from the previous billing." +msgstr "前回合計請求書の請求額。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields.selection,name:l10n_jp_summary_invoice_carryover.selection__res_partner__show_carryover_amounts__default +msgid "Use Company Default" +msgstr "会社のデフォルトを使用" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__use_payment_amount_manual +msgid "Use Manual Payment Amount" +msgstr "入金額を手入力" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,field_description:l10n_jp_summary_invoice_carryover.field_account_billing__use_prev_billed_amount_manual +msgid "Use Manual Previous Billed Amount" +msgstr "前回請求額を手入力" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_account_billing__show_carryover_amounts +msgid "Whether to show carryover amounts in the summary invoice report." +msgstr "合計請求書レポートに繰越額を表示するかどうか。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_res_partner__show_carryover_amounts +#: model:ir.model.fields,help:l10n_jp_summary_invoice_carryover.field_res_users__show_carryover_amounts +msgid "" +"Whether to show carryover amounts in the summary invoice report. If set to " +"'Use Company Default', the company setting will be used." +msgstr "合計請求書レポートに繰越額を表示するかどうか。「会社のデフォルトを使用」の場合は、会社の設定が使用されます。" + +#. module: l10n_jp_summary_invoice_carryover +#: model:ir.model.fields.selection,name:l10n_jp_summary_invoice_carryover.selection__res_partner__show_carryover_amounts__yes +msgid "Yes" +msgstr "はい" diff --git a/l10n_jp_summary_invoice_carryover/models/__init__.py b/l10n_jp_summary_invoice_carryover/models/__init__.py new file mode 100644 index 0000000..63cd2f3 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/models/__init__.py @@ -0,0 +1,4 @@ +from . import account_billing +from . import res_company +from . import res_config_settings +from . import res_partner diff --git a/l10n_jp_summary_invoice_carryover/models/account_billing.py b/l10n_jp_summary_invoice_carryover/models/account_billing.py new file mode 100644 index 0000000..7d36785 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/models/account_billing.py @@ -0,0 +1,170 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class AccountBilling(models.Model): + _inherit = "account.billing" + + prev_billing_id = fields.Many2one( + "account.billing", + string="Previous Billing", + compute="_compute_prev_billing_id", + store=True, + readonly=False, + domain="[('id', 'in', prev_billing_candidate_ids)]", + tracking=True, + ) + prev_billing_candidate_ids = fields.Many2many( + "account.billing", + string="Previous Billing Candidates", + compute="_compute_prev_billing_candidate_ids", + ) + use_prev_billed_amount_manual = fields.Boolean( + string="Use Manual Previous Billed Amount", + tracking=True, + copy=False, + ) + prev_billed_amount_manual = fields.Monetary( + string="Previous Billed Amount (Manual)", + tracking=True, + copy=False, + help="Manual override for previous billed amount.", + ) + prev_billed_amount = fields.Monetary( + string="Previous Billed Amount", + compute="_compute_carryover_amounts", + store=True, + help="Total billed amount from the previous billing.", + ) + use_payment_amount_manual = fields.Boolean( + string="Use Manual Payment Amount", + tracking=True, + copy=False, + ) + payment_amount_manual = fields.Monetary( + string="Payment Amount (Manual)", + tracking=True, + copy=False, + help="Manual override for payment amount.", + ) + payment_amount = fields.Monetary( + compute="_compute_carryover_amounts", + store=True, + help="Payments received on previous billing invoices.", + ) + carryover_amount = fields.Monetary( + compute="_compute_carryover_amounts", + store=True, + help="Previous billing amount minus payments.", + ) + total_billed_amount = fields.Monetary( + compute="_compute_carryover_amounts", + store=True, + recursive=True, + help="Carryover plus current billed amount.", + ) + show_carryover_amounts = fields.Boolean( + compute="_compute_show_carryover_amounts", + store=True, + readonly=False, + help="Whether to show carryover amounts in the summary invoice report.", + ) + + def _get_prev_billing_domain(self): + self.ensure_one() + return [ + ("company_id", "=", self.company_id.id), + ( + "partner_id.commercial_partner_id", + "=", + self.partner_id.commercial_partner_id.id, + ), + ("bill_type", "=", self.bill_type), + ("currency_id", "=", self.currency_id.id), + ("state", "=", "billed"), + ("date", "<", self.date or fields.Date.today()), + ("id", "!=", self.id), + ] + + @api.depends("partner_id", "bill_type", "currency_id", "date", "state") + def _compute_prev_billing_candidate_ids(self): + for rec in self: + rec.prev_billing_candidate_ids = self.search(rec._get_prev_billing_domain()) + + @api.depends("partner_id", "bill_type", "currency_id", "date", "state") + def _compute_prev_billing_id(self): + for rec in self: + rec.prev_billing_id = self.search( + rec._get_prev_billing_domain(), + order="date desc, id desc", + limit=1, + ) + + def _get_prev_billed_amount(self): + self.ensure_one() + return ( + self.prev_billed_amount_manual + if self.use_prev_billed_amount_manual + else self.prev_billed_amount + ) + + def _get_payment_amount(self): + self.ensure_one() + return ( + self.payment_amount_manual + if self.use_payment_amount_manual + else self.payment_amount + ) + + @api.depends( + "prev_billing_id", + "use_prev_billed_amount_manual", + "prev_billed_amount_manual", + "use_payment_amount_manual", + "payment_amount_manual", + "amount_total", + "prev_billing_id.total_billed_amount", + "prev_billing_id.billing_line_ids.amount_residual", + "prev_billing_id.tax_adjustment_entry_id.amount_residual", + ) + def _compute_carryover_amounts(self): + for rec in self: + rec.prev_billed_amount = 0.0 + rec.payment_amount = 0.0 + rec.carryover_amount = 0.0 + rec.total_billed_amount = rec.amount_total + prev_billing = rec.prev_billing_id + if prev_billing: + rec.prev_billed_amount = prev_billing.total_billed_amount + current_residual = ( + sum(line.amount_residual for line in prev_billing.billing_line_ids) + + prev_billing.tax_adjustment_entry_id.amount_residual + ) + rec.payment_amount = rec.prev_billed_amount - current_residual + prev_billed_amount = rec._get_prev_billed_amount() + payment_amount = rec._get_payment_amount() + rec.carryover_amount = prev_billed_amount - payment_amount + rec.total_billed_amount = rec.carryover_amount + rec.amount_total + + @api.depends("partner_id") + def _compute_show_carryover_amounts(self): + for rec in self: + partner = rec.partner_id.commercial_partner_id + if partner.show_carryover_amounts == "yes": + rec.show_carryover_amounts = True + elif partner.show_carryover_amounts == "no": + rec.show_carryover_amounts = False + else: + rec.show_carryover_amounts = rec.company_id.show_carryover_amounts + + def validate_billing(self): + for rec in self: + if not rec.use_prev_billed_amount_manual: + rec.use_prev_billed_amount_manual = True + rec.prev_billed_amount_manual = rec.prev_billed_amount + if not rec.use_payment_amount_manual: + rec.use_payment_amount_manual = True + rec.payment_amount_manual = rec.payment_amount + return super().validate_billing() diff --git a/l10n_jp_summary_invoice_carryover/models/res_company.py b/l10n_jp_summary_invoice_carryover/models/res_company.py new file mode 100644 index 0000000..64b568e --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + show_carryover_amounts = fields.Boolean( + default=True, + help="If enabled, carryover amount fields will be displayed in the summary " + "invoice report.", + ) diff --git a/l10n_jp_summary_invoice_carryover/models/res_config_settings.py b/l10n_jp_summary_invoice_carryover/models/res_config_settings.py new file mode 100644 index 0000000..709b6ed --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/models/res_config_settings.py @@ -0,0 +1,12 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + show_carryover_amounts = fields.Boolean( + related="company_id.show_carryover_amounts", readonly=False + ) diff --git a/l10n_jp_summary_invoice_carryover/models/res_partner.py b/l10n_jp_summary_invoice_carryover/models/res_partner.py new file mode 100644 index 0000000..4ffd6ff --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/models/res_partner.py @@ -0,0 +1,19 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + show_carryover_amounts = fields.Selection( + selection=[ + ("default", "Use Company Default"), + ("yes", "Yes"), + ("no", "No"), + ], + default="default", + help="Whether to show carryover amounts in the summary invoice report. " + "If set to 'Use Company Default', the company setting will be used.", + ) diff --git a/l10n_jp_summary_invoice_carryover/pyproject.toml b/l10n_jp_summary_invoice_carryover/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/l10n_jp_summary_invoice_carryover/readme/CONFIGURE.md b/l10n_jp_summary_invoice_carryover/readme/CONFIGURE.md new file mode 100644 index 0000000..b55c384 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/readme/CONFIGURE.md @@ -0,0 +1,16 @@ +The visibility of carryover amounts in the report can be controlled at three +levels: + +1. **Company** (default for all partners): Go to *Settings > Invoicing > + Japan Summary Invoice* and enable/disable **Show Carryover Amounts**. + +2. **Partner** (override for specific customers): Go to the partner form, + under *Sales & Purchases > Summary Invoice*, set **Show Carryover Amounts** + to: + - *Use Company Default*: Follow the company setting + - *Yes*: Always show carryover amounts + - *No*: Never show carryover amounts + +3. **Billing** (per-billing override): In the billing form under the + *Carryover* tab, the **Show Carryover Amounts** checkbox is computed + from the partner setting but can be manually adjusted. diff --git a/l10n_jp_summary_invoice_carryover/readme/CONTRIBUTORS.md b/l10n_jp_summary_invoice_carryover/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..c4020f4 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Quartile](https://www.quartile.co): + - Yoshi Tashiro diff --git a/l10n_jp_summary_invoice_carryover/readme/DESCRIPTION.md b/l10n_jp_summary_invoice_carryover/readme/DESCRIPTION.md new file mode 100644 index 0000000..ea1ed00 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/readme/DESCRIPTION.md @@ -0,0 +1,14 @@ +This module extends Japan Summary Invoice to support carryover amount tracking +for recurring billing cycles. + +It adds the following fields to billings: + +- **Previous Billed Amount**: Total from the previous billing period +- **Payment Amount**: Payments received against the previous billing +- **Carryover Amount**: Outstanding balance carried forward +- **Total Billed Amount**: Carryover plus current purchases + +Manual override fields are available to adjust values when needed. + +The visibility of carryover amounts in the report can be controlled at +company, partner, and billing levels. diff --git a/l10n_jp_summary_invoice_carryover/readme/USAGE.md b/l10n_jp_summary_invoice_carryover/readme/USAGE.md new file mode 100644 index 0000000..9796e6c --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/readme/USAGE.md @@ -0,0 +1,12 @@ +1. When creating a new billing, the system automatically finds the most + recent validated billing for the same partner and calculates: + - **Previous Billed Amount**: Total amount from the previous billing. + - **Payment Amount**: Payments received on the previous billing invoices. + - **Carryover Amount**: Previous billed amount minus payments. +2. Use the **Manual Adj.** toggle to manually override the computed values + if needed. This is useful when the previous billing was created before + this module was installed or when adjustments are required. +3. When the billing is validated, the current computed values are + automatically frozen by enabling the manual override toggles. This + ensures that subsequent payments on previous invoices do not affect + the validated billing's carryover amounts. diff --git a/l10n_jp_summary_invoice_carryover/reports/report_summary_invoice_templates.xml b/l10n_jp_summary_invoice_carryover/reports/report_summary_invoice_templates.xml new file mode 100644 index 0000000..9dd453d --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/reports/report_summary_invoice_templates.xml @@ -0,0 +1,89 @@ + + + diff --git a/l10n_jp_summary_invoice_carryover/static/description/index.html b/l10n_jp_summary_invoice_carryover/static/description/index.html new file mode 100644 index 0000000..ad79a94 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/static/description/index.html @@ -0,0 +1,487 @@ + + + + + +Japan Summary Invoice - Carryover + + + +
+

Japan Summary Invoice - Carryover

+ + +

Alpha License: AGPL-3 OCA/l10n-japan Translate me on Weblate Try me on Runboat

+

This module extends Japan Summary Invoice to support carryover amount +tracking for recurring billing cycles.

+

It adds the following fields to billings:

+
    +
  • Previous Billed Amount: Total from the previous billing period
  • +
  • Payment Amount: Payments received against the previous billing
  • +
  • Carryover Amount: Outstanding balance carried forward
  • +
  • Total Billed Amount: Carryover plus current purchases
  • +
+

Manual override fields are available to adjust values when needed.

+

The visibility of carryover amounts in the report can be controlled at +company, partner, and billing levels.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

The visibility of carryover amounts in the report can be controlled at +three levels:

+
    +
  1. Company (default for all partners): Go to Settings > Invoicing > +Japan Summary Invoice and enable/disable Show Carryover Amounts.
  2. +
  3. Partner (override for specific customers): Go to the partner +form, under Sales & Purchases > Summary Invoice, set Show +Carryover Amounts to:
      +
    • Use Company Default: Follow the company setting
    • +
    • Yes: Always show carryover amounts
    • +
    • No: Never show carryover amounts
    • +
    +
  4. +
  5. Billing (per-billing override): In the billing form under the +Carryover tab, the Show Carryover Amounts checkbox is computed +from the partner setting but can be manually adjusted.
  6. +
+
+
+

Usage

+
    +
  1. When creating a new billing, the system automatically finds the most +recent validated billing for the same partner and calculates:
      +
    • Previous Billed Amount: Total amount from the previous +billing.
    • +
    • Payment Amount: Payments received on the previous billing +invoices.
    • +
    • Carryover Amount: Previous billed amount minus payments.
    • +
    +
  2. +
  3. Use the Manual Adj. toggle to manually override the computed +values if needed. This is useful when the previous billing was +created before this module was installed or when adjustments are +required.
  4. +
  5. When the billing is validated, the current computed values are +automatically frozen by enabling the manual override toggles. This +ensures that subsequent payments on previous invoices do not affect +the validated billing’s carryover amounts.
  6. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Quartile
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/l10n-japan project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/l10n_jp_summary_invoice_carryover/tests/__init__.py b/l10n_jp_summary_invoice_carryover/tests/__init__.py new file mode 100644 index 0000000..bba38fd --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/tests/__init__.py @@ -0,0 +1 @@ +from . import test_l10n_jp_summary_invoice_carryover diff --git a/l10n_jp_summary_invoice_carryover/tests/test_l10n_jp_summary_invoice_carryover.py b/l10n_jp_summary_invoice_carryover/tests/test_l10n_jp_summary_invoice_carryover.py new file mode 100644 index 0000000..fdb697d --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/tests/test_l10n_jp_summary_invoice_carryover.py @@ -0,0 +1,226 @@ +# Copyright 2025 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import date + +from odoo.fields import Command +from odoo.tests.common import TransactionCase + + +class TestSummaryInvoiceCarryover(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env["res.company"].create( + { + "name": "test company", + "currency_id": cls.env.ref("base.JPY").id, + "country_id": cls.env.ref("base.jp").id, + "tax_calculation_rounding_method": "round_globally", + } + ) + cls.env = cls.env( + context={"allowed_company_ids": [cls.company.id], "tracking_disable": True} + ) + account_receivable = cls.env["account.account"].create( + {"code": "recv", "name": "Receivable", "account_type": "asset_receivable"} + ) + cls.account_income = cls.env["account.account"].create( + {"code": "income", "name": "Income", "account_type": "income"} + ) + account_bank = cls.env["account.account"].create( + {"code": "bank", "name": "Bank", "account_type": "asset_cash"} + ) + cls.partner = cls.env["res.partner"].create( + { + "name": "Test Partner", + "property_account_receivable_id": account_receivable.id, + } + ) + tax_group = cls.env["account.tax.group"].create({"name": "Tax Group"}) + cls.tax_10 = cls.env["account.tax"].create( + { + "name": "Test Tax 10%", + "amount": 10.0, + "type_tax_use": "sale", + "company_id": cls.company.id, + "tax_group_id": tax_group.id, + } + ) + cls.env["account.journal"].create( + {"code": "SALE", "name": "Sales Journal", "type": "sale"} + ) + cls.bank_journal = cls.env["account.journal"].create( + { + "code": "BNK", + "name": "Bank Journal", + "type": "bank", + "default_account_id": account_bank.id, + "inbound_payment_method_line_ids": [ + Command.create( + { + "payment_method_id": cls.env.ref( + "account.account_payment_method_manual_in" + ).id, + "payment_account_id": account_bank.id, + } + ) + ], + } + ) + + def _create_invoice(self, amount, tax, move_type="out_invoice"): + invoice = self.env["account.move"].create( + { + "move_type": move_type, + "partner_id": self.partner.id, + "invoice_line_ids": [ + Command.create( + { + "name": "test line", + "account_id": self.account_income.id, + "quantity": 1, + "price_unit": amount, + "tax_ids": [Command.set(tax.ids)], + } + ) + ], + } + ) + invoice.action_post() + return invoice + + def _create_billing(self, amount_or_invoices, billing_date=None, validate=False): + """Create a billing. If amount is given, create an invoice first.""" + if isinstance(amount_or_invoices, int | float): + invoices = self._create_invoice(amount_or_invoices, self.tax_10) + else: + invoices = amount_or_invoices + billing = self.env["account.billing"].create( + { + "partner_id": self.partner.id, + "bill_type": "out_invoice", + "date": billing_date or date(2025, 1, 15), + "billing_line_ids": [ + Command.create({"move_id": inv.id}) for inv in invoices + ], + } + ) + if validate: + billing.validate_billing() + self.env.flush_all() + return billing + + def _register_payment(self, invoice, amount): + """Register a payment for an invoice.""" + payment = self.env["account.payment"].create( + { + "journal_id": self.bank_journal.id, + "partner_id": invoice.partner_id.id, + "amount": amount, + "payment_type": "inbound", + "partner_type": "customer", + } + ) + payment.action_post() + receivable_line = invoice.line_ids.filtered( + lambda x: x.account_id.account_type == "asset_receivable" + ) + payment_line = payment.move_id.line_ids.filtered( + lambda x: x.account_id.account_type == "asset_receivable" + ) + (receivable_line + payment_line).reconcile() + return payment + + def test_compute_prev_billing_id(self): + billing1 = self._create_billing(1000, date(2025, 1, 15), validate=True) + self.assertFalse(billing1.prev_billing_id) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + self.assertEqual(billing2.prev_billing_id, billing1) + + def test_compute_carryover_amounts_no_previous(self): + billing = self._create_billing(1000) + self.assertEqual(billing.prev_billed_amount, 0) + self.assertEqual(billing.payment_amount, 0) + self.assertEqual(billing.carryover_amount, 0) + self.assertEqual(billing.total_billed_amount, 1100) + + def test_compute_carryover_amounts_with_previous(self): + self._create_billing(1000, date(2025, 1, 15), validate=True) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + self.assertEqual(billing2.prev_billed_amount, 1100) + self.assertEqual(billing2._get_prev_billed_amount(), 1100) + self.assertEqual(billing2.payment_amount, 0) + self.assertEqual(billing2._get_payment_amount(), 0) + self.assertEqual(billing2.carryover_amount, 1100) + # Total billed amount is previous total plus current total + self.assertEqual(billing2.total_billed_amount, 3300) + + def test_compute_carryover_amounts_with_partial_payment(self): + billing1 = self._create_billing(1000, date(2025, 1, 15), validate=True) + self._register_payment(billing1.billing_line_ids.move_id, 500) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + self.assertEqual(billing2.prev_billed_amount, 1100) + self.assertEqual(billing2.payment_amount, 500) + self.assertEqual(billing2.carryover_amount, 600) + + def test_compute_carryover_amounts_with_credit_note(self): + # Prev billing: invoice 1000 (1100 with tax) + credit note 200 (220 with tax) + # Net: 1100 - 220 = 880 + invoice = self._create_invoice(1000, self.tax_10) + credit_note = self._create_invoice(200, self.tax_10, move_type="out_refund") + billing1 = self._create_billing( + invoice + credit_note, date(2025, 1, 15), validate=True + ) + self.assertEqual(billing1.amount_total, 880) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + self.assertEqual(billing2.prev_billed_amount, 880) + self.assertEqual(billing2.payment_amount, 0) + self.assertEqual(billing2.carryover_amount, 880) + + def test_manual_override_prev_billed_amount(self): + self._create_billing(1000, date(2025, 1, 15), validate=True) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + billing2.use_prev_billed_amount_manual = True + billing2.prev_billed_amount_manual = 5000 + self.assertEqual(billing2.carryover_amount, 5000) + self.assertEqual(billing2._get_prev_billed_amount(), 5000) + # Test that zero override works (not treated as falsy) + billing2.prev_billed_amount_manual = 0 + self.assertEqual(billing2._get_prev_billed_amount(), 0) + self.assertEqual(billing2.carryover_amount, 0) + + def test_manual_override_payment_amount(self): + self._create_billing(1000, date(2025, 1, 15), validate=True) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + billing2.use_payment_amount_manual = True + billing2.payment_amount_manual = 300 + self.assertEqual(billing2._get_payment_amount(), 300) + self.assertEqual(billing2.carryover_amount, 800) + + def test_prev_billing_candidates(self): + billing1 = self._create_billing(1000, date(2025, 1, 15), validate=True) + billing2 = self._create_billing(1500, date(2025, 2, 15), validate=True) + billing3 = self._create_billing(2000, date(2025, 3, 15)) + self.assertIn(billing1, billing3.prev_billing_candidate_ids) + self.assertIn(billing2, billing3.prev_billing_candidate_ids) + self.assertEqual(billing3.prev_billing_id, billing2) + + def test_validate_freezes_carryover_amounts(self): + """Validation copies computed values to manual fields to freeze them.""" + billing1 = self._create_billing(1000, date(2025, 1, 15), validate=True) + billing2 = self._create_billing(2000, date(2025, 2, 15)) + self.assertFalse(billing2.use_prev_billed_amount_manual) + self.assertFalse(billing2.use_payment_amount_manual) + self.assertEqual(billing2.carryover_amount, 1100) + billing2.validate_billing() + self.env.flush_all() + # Toggles are now on with frozen values + self.assertTrue(billing2.use_prev_billed_amount_manual) + self.assertTrue(billing2.use_payment_amount_manual) + self.assertEqual(billing2.prev_billed_amount_manual, 1100) + self.assertEqual(billing2.payment_amount_manual, 0) + # Register payment on billing1 after billing2 is validated + self._register_payment(billing1.billing_line_ids.move_id, 500) + # Carryover amounts remain frozen + self.assertEqual(billing2.carryover_amount, 1100) diff --git a/l10n_jp_summary_invoice_carryover/views/account_billing_views.xml b/l10n_jp_summary_invoice_carryover/views/account_billing_views.xml new file mode 100644 index 0000000..9a8148b --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/views/account_billing_views.xml @@ -0,0 +1,67 @@ + + + + account.billing.form.carryover + account.billing + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_jp_summary_invoice_carryover/views/res_config_settings_views.xml b/l10n_jp_summary_invoice_carryover/views/res_config_settings_views.xml new file mode 100644 index 0000000..8620fd0 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/views/res_config_settings_views.xml @@ -0,0 +1,23 @@ + + + + res.config.settings.view.form - l10n_jp_summary_invoice_carryover + res.config.settings + + + + + + + + + + diff --git a/l10n_jp_summary_invoice_carryover/views/res_partner_views.xml b/l10n_jp_summary_invoice_carryover/views/res_partner_views.xml new file mode 100644 index 0000000..43287f1 --- /dev/null +++ b/l10n_jp_summary_invoice_carryover/views/res_partner_views.xml @@ -0,0 +1,18 @@ + + + + res.partner.form.carryover + res.partner + + + + + + + + + +