paso a pos.sessions más natural, los apuntes están ahí.
This commit is contained in:
parent
62e0e8a92c
commit
80f24bcc46
2 changed files with 42 additions and 36 deletions
|
|
@ -1,27 +1,32 @@
|
||||||
# POS Account Payment Order SEPA
|
# POS Account Payment Order SEPA
|
||||||
|
|
||||||
Adds a contextual action **"Collect by SEPA direct debit"** on the POS orders
|
Adds a contextual action **"Collect by SEPA direct debit"** on the POS
|
||||||
list/form (Point of Sale → Orders).
|
sessions list/form (Point of Sale → Sessions).
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
When a POS order is paid with a *"Customer Account"* payment method
|
When a POS order is paid with a *"Customer Account"* payment method
|
||||||
(`split_transactions = True`), Odoo does not register a real collection: it
|
(`split_transactions = True`) and is **not invoiced**, Odoo does not register a
|
||||||
leaves the amount as an **open receivable** for the customer inside the
|
real collection nor a per-order accounting move: the amount lands as an **open
|
||||||
session's journal entry. This module lets you collect those receivables in
|
receivable** for the customer inside the **session's** single journal entry
|
||||||
bulk through a **SEPA direct debit payment order** (debit order), without
|
(`pos.session.move_id`). This module lets you collect those receivables in bulk
|
||||||
|
through a **SEPA direct debit payment order** (debit order), without
|
||||||
re-invoicing the already-issued (simplified) tickets.
|
re-invoicing the already-issued (simplified) tickets.
|
||||||
|
|
||||||
|
> The action runs on **sessions** (not orders) precisely because the receivable
|
||||||
|
> lives in the session move, not in the order. Selecting the sessions of the
|
||||||
|
> affected period collects every open "Customer Account" line in them.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Make sure the affected customers have a **valid SEPA mandate** (see
|
1. Make sure the affected customers have a **valid SEPA mandate** (see
|
||||||
`account_banking_mandate_batch`).
|
`account_banking_mandate_batch`).
|
||||||
2. Go to **Point of Sale → Orders**, filter the orders paid on account, select
|
2. Go to **Point of Sale → Sessions**, filter the sessions of the period,
|
||||||
them and run **Action → Collect by SEPA direct debit**.
|
select them and run **Action → Collect by SEPA direct debit**.
|
||||||
3. The action creates the **payment lines** inside a draft inbound SEPA
|
3. The action creates the **payment lines** inside a draft inbound SEPA
|
||||||
payment order, grouped by payment mode. Orders without an open receivable,
|
payment order, grouped by payment mode. Receivable lines without a partner,
|
||||||
without a partner, already queued, or whose partner has no valid mandate are
|
already queued, or whose partner has no valid mandate are **skipped and
|
||||||
**skipped and reported**.
|
reported**. Already-collected lines (reconciled) are ignored.
|
||||||
4. Open **Invoicing → Customers → Payment Orders**, confirm it and **generate
|
4. Open **Invoicing → Customers → Payment Orders**, confirm it and **generate
|
||||||
the SEPA file (pain.008)**.
|
the SEPA file (pain.008)**.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,37 +2,38 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="action_pos_collect_sepa" model="ir.actions.server">
|
<record id="action_pos_collect_sepa" model="ir.actions.server">
|
||||||
<field name="name">Collect by SEPA direct debit</field>
|
<field name="name">Collect by SEPA direct debit</field>
|
||||||
<field name="model_id" ref="point_of_sale.model_pos_order" />
|
<field name="model_id" ref="point_of_sale.model_pos_session" />
|
||||||
<field name="binding_model_id" ref="point_of_sale.model_pos_order" />
|
<field name="binding_model_id" ref="point_of_sale.model_pos_session" />
|
||||||
<field name="binding_view_types">list,form</field>
|
<field name="binding_view_types">list,form</field>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code"><![CDATA[
|
<field name="code"><![CDATA[
|
||||||
# Collect the still-open "Customer Account" receivables generated by the
|
# Collect the still-open "Customer Account" receivables of the selected POS
|
||||||
# selected POS orders through a SEPA direct debit payment order (debit
|
# sessions through a SEPA direct debit payment order (debit order).
|
||||||
# order). For each order we take the receivable journal item of its session
|
|
||||||
# move, keep the unreconciled ones whose partner has a valid SEPA mandate,
|
|
||||||
# and create the payment lines inside a single draft inbound payment order.
|
|
||||||
# The SEPA XML (pain.008) is generated afterwards from that order by the user.
|
|
||||||
#
|
#
|
||||||
# Orders are skipped (and reported) when: no accounting move yet, no open
|
# Unfactured POS orders do not get their own accounting move: the receivable
|
||||||
|
# generated by a "Customer Account" payment method (split_transactions) lands
|
||||||
|
# in the SINGLE journal entry of the session (session.move_id). So we read the
|
||||||
|
# open receivable lines from that move, keep the ones whose partner has a valid
|
||||||
|
# SEPA mandate, and create the payment lines inside a draft inbound payment
|
||||||
|
# order. The SEPA file (pain.008) is generated afterwards from that order.
|
||||||
|
#
|
||||||
|
# Lines are skipped (and reported) when: the session has no move yet, no open
|
||||||
# receivable line, no partner, the line is already in a payment order, or the
|
# receivable line, no partner, the line is already in a payment order, or the
|
||||||
# partner has no valid SEPA mandate. Refunds/credit lines are ignored.
|
# partner has no valid SEPA mandate.
|
||||||
|
|
||||||
Mode = env["account.payment.mode"]
|
Mode = env["account.payment.mode"]
|
||||||
Order = env["account.payment.order"]
|
Order = env["account.payment.order"]
|
||||||
Mandate = env["account.banking.mandate"]
|
Mandate = env["account.banking.mandate"]
|
||||||
|
PaymentLine = env["account.payment.line"]
|
||||||
|
|
||||||
# --- Resolve the inbound SEPA payment mode (partner mode, with fallback) ---
|
# --- Resolve the inbound SEPA payment mode (partner mode, with fallback) ---
|
||||||
def sepa_mode_for(partner):
|
def is_sepa_inbound(mode):
|
||||||
mode = partner.customer_payment_mode_id
|
return bool(
|
||||||
if (
|
|
||||||
mode
|
mode
|
||||||
and mode.payment_order_ok
|
and mode.payment_order_ok
|
||||||
and mode.payment_method_id.code == "sepa_direct_debit"
|
and mode.payment_method_id.code == "sepa_direct_debit"
|
||||||
and mode.payment_method_id.payment_type == "inbound"
|
and mode.payment_method_id.payment_type == "inbound"
|
||||||
):
|
)
|
||||||
return mode
|
|
||||||
return False
|
|
||||||
|
|
||||||
fallback_mode = Mode.search(
|
fallback_mode = Mode.search(
|
||||||
[
|
[
|
||||||
|
|
@ -43,12 +44,12 @@ fallback_mode = Mode.search(
|
||||||
limit=1,
|
limit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Classify the selected orders ---
|
# --- Collect open receivable lines from the selected sessions' moves ---
|
||||||
lines_by_mode = {}
|
lines_by_mode = {}
|
||||||
skipped = {"no_move": 0, "no_line": 0, "no_partner": 0, "already": 0, "no_mandate": 0, "no_mode": 0}
|
skipped = {"no_move": 0, "no_line": 0, "no_partner": 0, "already": 0, "no_mandate": 0, "no_mode": 0}
|
||||||
|
|
||||||
for order in records:
|
for session in records:
|
||||||
move = order.account_move
|
move = session.move_id
|
||||||
if not move:
|
if not move:
|
||||||
skipped["no_move"] += 1
|
skipped["no_move"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
@ -65,7 +66,7 @@ for order in records:
|
||||||
if not partner:
|
if not partner:
|
||||||
skipped["no_partner"] += 1
|
skipped["no_partner"] += 1
|
||||||
continue
|
continue
|
||||||
if env["account.payment.line"].search_count(
|
if PaymentLine.search_count(
|
||||||
[("move_line_id", "=", line.id), ("order_id.state", "!=", "cancel")]
|
[("move_line_id", "=", line.id), ("order_id.state", "!=", "cancel")]
|
||||||
):
|
):
|
||||||
skipped["already"] += 1
|
skipped["already"] += 1
|
||||||
|
|
@ -75,7 +76,8 @@ for order in records:
|
||||||
):
|
):
|
||||||
skipped["no_mandate"] += 1
|
skipped["no_mandate"] += 1
|
||||||
continue
|
continue
|
||||||
mode = sepa_mode_for(partner) or fallback_mode
|
partner_mode = partner.customer_payment_mode_id
|
||||||
|
mode = partner_mode if is_sepa_inbound(partner_mode) else fallback_mode
|
||||||
if not mode:
|
if not mode:
|
||||||
skipped["no_mode"] += 1
|
skipped["no_mode"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
@ -86,7 +88,6 @@ for order in records:
|
||||||
created_lines = 0
|
created_lines = 0
|
||||||
order_names = []
|
order_names = []
|
||||||
for mode_id, move_lines in lines_by_mode.items():
|
for mode_id, move_lines in lines_by_mode.items():
|
||||||
mode = Mode.browse(mode_id)
|
|
||||||
payment_order = Order.search(
|
payment_order = Order.search(
|
||||||
[("payment_mode_id", "=", mode_id), ("state", "=", "draft")], limit=1
|
[("payment_mode_id", "=", mode_id), ("state", "=", "draft")], limit=1
|
||||||
)
|
)
|
||||||
|
|
@ -101,9 +102,9 @@ message = (
|
||||||
"Payment lines created: %(created)s\n"
|
"Payment lines created: %(created)s\n"
|
||||||
"Payment order(s): %(orders)s\n\n"
|
"Payment order(s): %(orders)s\n\n"
|
||||||
"Skipped:\n"
|
"Skipped:\n"
|
||||||
" no accounting move: %(no_move)s\n"
|
" session without accounting move: %(no_move)s\n"
|
||||||
" no open receivable line: %(no_line)s\n"
|
" session without open receivable line: %(no_line)s\n"
|
||||||
" no partner: %(no_partner)s\n"
|
" line without partner: %(no_partner)s\n"
|
||||||
" already in a payment order: %(already)s\n"
|
" already in a payment order: %(already)s\n"
|
||||||
" no valid SEPA mandate: %(no_mandate)s\n"
|
" no valid SEPA mandate: %(no_mandate)s\n"
|
||||||
" no SEPA payment mode: %(no_mode)s"
|
" no SEPA payment mode: %(no_mode)s"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue