odooapps/sale_order_batch/models/sale_order_batch.py

264 lines
9.6 KiB
Python

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.addons.sale.models.sale_order import INVOICE_STATUS
STATES = [
("open", "Open"),
("in_progress", "In Progress"),
("closed", "Closed"),
("cancel", "Cancelled"),
]
READONLY_FIELD_STATES = {state: [("readonly", True)] for state in {"closed"}}
class SaleOrderBatch(models.Model):
"""Group serveral Sale Orders into a batch"""
_name = "sale.order.batch"
_description = "Sales Order Batch"
_inherit = ["mail.thread", "mail.activity.mixin"]
_order = "date_order desc, id desc"
_check_company_auto = True
name = fields.Char(
string="Order Batch Reference",
required=True,
copy=False,
readonly=True,
index="trigram",
default=lambda self: _("New"),
)
company_id = fields.Many2one(
comodel_name="res.company",
required=True,
index=True,
default=lambda self: self.env.company,
)
state = fields.Selection(
selection=STATES,
string="Status",
readonly=True,
copy=False,
index=True,
tracking=1,
default="open",
)
date_order = fields.Datetime(
string="Order Date",
required=True,
readonly=False,
copy=False,
states=READONLY_FIELD_STATES,
help="Creation date of order batch,\nConfirmation date of confirmed orders.",
default=fields.Datetime.now,
)
sale_order_ids = fields.One2many("sale.order", "batch_id")
sale_order_count = fields.Integer(compute="_compute_sale_order_count")
sale_order_line_ids = fields.One2many(
"sale.order.line", "batch_id", states=READONLY_FIELD_STATES
)
invoice_ids = fields.Many2many("account.move", compute="_compute_invoice_ids")
invoice_count = fields.Integer(compute="_compute_invoice_ids")
invoice_status = fields.Selection(
selection=INVOICE_STATUS, compute="_compute_invoice_status", store=True
)
amount_total = fields.Float(compute="_compute_amount_total", string="Total")
product_ids = fields.One2many("sale.order.batch.product", "batch_id")
product_count = fields.Integer(compute="_compute_product_count")
partner_credit_warning = fields.Text(compute="_compute_partner_credit_warning")
@api.depends("sale_order_ids")
def _compute_sale_order_count(self):
for batch in self:
batch.sale_order_count = len(batch.sale_order_ids)
@api.depends("sale_order_ids.invoice_ids")
def _compute_invoice_ids(self):
for batch in self:
invoices = batch.sale_order_ids.mapped("invoice_ids")
batch.invoice_ids = invoices
batch.invoice_count = len(invoices)
@api.depends("sale_order_ids.invoice_status")
def _compute_invoice_status(self):
for batch in self:
if batch.sale_order_ids and any(
invoice_status == "to invoice"
for invoice_status in batch.sale_order_ids.mapped("invoice_status")
):
batch.invoice_status = "to invoice"
elif batch.sale_order_ids and all(
invoice_status == "invoiced"
for invoice_status in batch.sale_order_ids.mapped("invoice_status")
):
batch.invoice_status = "invoiced"
elif batch.sale_order_ids and all(
invoice_status in ("invoiced", "upselling")
for invoice_status in batch.sale_order_ids.mapped("invoice_status")
):
batch.invoice_status = "upselling"
else:
batch.invoice_status = "no"
@api.depends("sale_order_ids.amount_total")
def _compute_amount_total(self):
for batch in self:
batch.amount_total = sum(batch.sale_order_ids.mapped("amount_total"))
@api.depends("product_ids")
def _compute_product_count(self):
for batch in self:
batch.product_count = len(batch.product_ids)
@api.depends("sale_order_ids.partner_credit_warning")
def _compute_partner_credit_warning(self):
for batch in self:
batch.partner_credit_warning = ""
for order in batch.sale_order_ids:
if order.partner_credit_warning:
batch.partner_credit_warning += order.partner_credit_warning + "\n"
def action_view_source_sale_orders(self):
self.ensure_one()
result = self.env["ir.actions.act_window"]._for_xml_id("sale.action_orders")
if len(self.sale_order_ids) > 1:
result["domain"] = [("id", "in", self.sale_order_ids.ids)]
elif len(self.sale_order_ids) == 1:
result["views"] = [(self.env.ref("sale.view_order_form", False).id, "form")]
result["res_id"] = self.sale_order_ids.id
else:
result = {"type": "ir.actions.act_window_close"}
return result
def action_view_products(self):
self.ensure_one()
result = self.env["ir.actions.act_window"]._for_xml_id(
"product.product_normal_action_sell"
)
if len(self.product_ids) > 1:
result["domain"] = [("id", "in", self.product_ids.ids)]
elif len(self.product_ids) == 1:
result["views"] = [
(self.env.ref("product.product_product_tree_view", False).id, "form")
]
result["res_id"] = self.product_ids.id
else:
result = {"type": "ir.actions.act_window_close"}
return result
def action_view_invoice(self):
invoices = self.mapped("invoice_ids")
action = self.env["ir.actions.actions"]._for_xml_id(
"account.action_move_out_invoice_type"
)
if len(invoices) > 1:
action["domain"] = [("id", "in", invoices.ids)]
elif len(invoices) == 1:
form_view = [(self.env.ref("account.view_move_form").id, "form")]
if "views" in action:
action["views"] = form_view + [
(state, view) for state, view in action["views"] if view != "form"
]
else:
action["views"] = form_view
action["res_id"] = invoices.id
else:
action = {"type": "ir.actions.act_window_close"}
action["context"] = {"default_move_type": "out_invoice"}
return action
def action_in_progress(self):
for batch in self:
batch.sale_order_line_ids._validate_analytic_distribution()
orders = batch.with_context(bypass_batch=True).sale_order_ids
orders.action_quotation_sent()
batch.update({"state": "in_progress"})
return True
def action_confirm(self):
for batch in self:
orders = batch.with_context(bypass_batch=True).sale_order_ids
orders.action_confirm()
batch.update({"state": "closed"})
return True
def action_cancel(self):
cancel_warning = self._show_cancel_wizard()
if cancel_warning:
self.ensure_one()
return {
"name": _("Cancel %s", self.name),
"view_mode": "form",
"type": "ir.actions.act_window",
"res_model": "sale.order.batch.cancel.wizard",
"target": "new",
"context": {"active_ids": self.ids},
}
else:
self._action_cancel()
def _action_cancel(self):
orders = self.mapped("sale_order_ids")
# use _action_cancel to skip the wizzard
orders._action_cancel()
self.write({"state": "cancel"})
def _show_cancel_wizard(self):
"""Decide whether the sale.order.batch.cancel wizard should be shown to cancel
specified orders.
:return: True if there is any non-draft order in the given orders
:rtype: bool
"""
if self.env.context.get("disable_cancel_warning"):
return False
batch_orders = self.mapped("sale_order_ids")
return any(order._show_cancel_wizard() for order in batch_orders)
def action_open(self):
for batch in self:
orders = batch.sale_order_ids
orders.action_draft()
batch.update({"state": "open"})
return True
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if "company_id" in vals:
self = self.with_company(vals["company_id"])
if vals.get("name", _("New")) == _("New"):
vals["name"] = self.env["ir.sequence"].next_by_code(
"sale.order.batch"
) or _("New")
return super().create(vals_list)
class SaleOrderBatchCancelWizard(models.TransientModel):
_name = "sale.order.batch.cancel.wizard"
_description = "Sale Order Batch Cancel Wizard"
def action_cancel(self):
batches = self.env["sale.order.batch"].browse(self._context["active_ids"])
for batch in batches:
orders = batch.sale_order_ids
# Warn if any sale order is not in draft
not_draft_orders = orders.filtered(lambda o: o.state != "draft")
if not_draft_orders:
raise UserError(
_(
"You can only cancel batches where all sale orders are in "
"draft state."
)
)
# use _action_cancel to skip the wizzard
# ToDo: Implement wizard logic
orders._action_cancel()
batch.update({"state": "cancel"})
return True
def action_discard(self):
return {"type": "ir.actions.act_window_close"}