|
| 1 | +# Copyright 2025 Quartile (https://www.quartile.co) |
| 2 | +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
| 3 | + |
| 4 | +from odoo import fields, models |
| 5 | +from collections import defaultdict |
| 6 | + |
| 7 | + |
| 8 | +class AccountBillingCutoff(models.TransientModel): |
| 9 | + _name = "wiz.account.billing.cutoff" |
| 10 | + _description = "Account Billing From Cutoff" |
| 11 | + |
| 12 | + cutoff_date = fields.Date(required=True, default=fields.Date.context_today) |
| 13 | + bill_type = fields.Selection( |
| 14 | + selection=[("out_invoice", "Customer Invoice"), ("in_invoice", "Vendor Bill")], |
| 15 | + readonly=True, |
| 16 | + default=lambda self: self._context.get("bill_type", False), |
| 17 | + ) |
| 18 | + |
| 19 | + def _allowed_move_types(self): |
| 20 | + if self.bill_type == "out_invoice": |
| 21 | + return ["out_invoice", "out_refund"] |
| 22 | + return ["in_invoice", "in_refund"] |
| 23 | + |
| 24 | + def _search_moves_domain(self): |
| 25 | + return [ |
| 26 | + ("partner_id.is_summary_invoicing", "=", True), |
| 27 | + ("company_id", "=", self.env.company.id), |
| 28 | + ("state", "=", "posted"), |
| 29 | + ("payment_state", "!=", "paid"), |
| 30 | + ("move_type", "in", self._allowed_move_types()), |
| 31 | + ("cutoff_date", "<=", self.cutoff_date), |
| 32 | + ] |
| 33 | + |
| 34 | + def action_create_billings(self): |
| 35 | + Move = self.env["account.move"] |
| 36 | + moves = Move.search(self._search_moves_domain(), order="partner_id, currency_id, id") |
| 37 | + moves = moves.filtered( |
| 38 | + lambda m: not any(b.state in ("draft", "billed") for b in m.billing_ids) |
| 39 | + ) |
| 40 | + if not moves: |
| 41 | + return {"type": "ir.actions.act_window_close"} |
| 42 | + groups = defaultdict(lambda: self.env["account.move"]) |
| 43 | + for m in moves: |
| 44 | + groups[(m.partner_id.id, m.currency_id.id, m.move_type)] |= m |
| 45 | + created = self.env["account.billing"] |
| 46 | + for (partner_id, _currency_id, _move_type), recs in groups.items(): |
| 47 | + partner = self.env["res.partner"].browse(partner_id) |
| 48 | + billing = recs._create_billing(partner) |
| 49 | + created |= billing |
| 50 | + return { |
| 51 | + "type": "ir.actions.act_window", |
| 52 | + "name": "Billings", |
| 53 | + "res_model": "account.billing", |
| 54 | + "view_mode": "tree,form", |
| 55 | + "domain": [("id", "in", created.ids)], |
| 56 | + "target": "current", |
| 57 | + } |
0 commit comments