fix the splitting journal entry if there are discount product

This commit is contained in:
admin.suherdy 2025-11-07 09:50:30 +07:00
parent 074a918b82
commit 116b132b30
10 changed files with 632 additions and 427 deletions

278
.gitignore vendored
View File

@ -1,140 +1,140 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
build/
temp/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
Pipfile.lock
# poetry
poetry.lock
# PEP 582; used by python-next
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# VS Code settings
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
build/
temp/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
Pipfile.lock
# poetry
poetry.lock
# PEP 582; used by python-next
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# VS Code settings
.vscode/

80
CONFIGURATION.md Normal file
View File

@ -0,0 +1,80 @@
# Split Pendapatan Payment - Configuration Guide
## Issue Fixed
The module had a logic issue where it wasn't properly applying income accounts from payment methods for regular (non-discount) products when a discount product was configured in the POS.
## Root Cause
The original code used `elif` logic which meant:
- IF discount product exists AND current sale is for discount product → use discount_account_id
- ELIF discount product exists BUT current sale is NOT discount → use income_account_id
- However, the ELIF was actually inside the discount check, causing it to skip for regular products
## Solution Applied
Fixed the logic flow to properly check:
1. Is this a discount product sale? → Use `discount_account_id` from payment method (or product default)
2. Is this a regular product sale? → Use `income_account_id` from payment method (or product default)
3. No discount product configured? → Use `income_account_id` from payment method (or product default)
## Required Configuration
To use this module properly, you MUST configure the income accounts in your payment methods:
### Step 1: Configure Payment Method Income Accounts
1. Go to **Point of Sale → Configuration → Payment Methods**
2. For each payment method (Cash, Bank, Credit Card, etc.), open the form
3. Fill in the following fields:
- **Income Account**: The account to use for regular product sales with this payment method
- **Discount Account**: The account to use for discount product sales with this payment method (if you use POS discounts)
### Step 2: Important Notes
- If you don't configure these fields, the module will fall back to using the product's default income account
- For discount products, if `discount_account_id` is not set in the payment method, it will use the discount product's configured account
- For regular products, if `income_account_id` is not set in the payment method, it will use the product's configured income account (from product or product category)
### Example Configuration
**Cash Payment Method:**
- Income Account: 4000 - Sales Revenue (Cash)
- Discount Account: 4100 - Sales Discounts (Cash)
**Bank Payment Method:**
- Income Account: 4010 - Sales Revenue (Bank)
- Discount Account: 4110 - Sales Discounts (Bank)
**Credit Card Payment Method:**
- Income Account: 4020 - Sales Revenue (Credit Card)
- Discount Account: 4120 - Sales Discounts (Credit Card)
## Error Messages
If you see an error saying "need to define the income account at product level", it means:
1. The payment method doesn't have `income_account_id` configured, AND
2. The product doesn't have a valid income account defined
**Solution**: Configure the income account in either:
- The payment method form (recommended), OR
- The product form or product category
## Diagnostic Logging
The module now includes detailed logging to help diagnose issues. Check your Odoo logs for entries starting with `=== SPLIT PENDAPATAN DEBUG ===` to see:
- Which payment method is being processed
- What accounts are configured
- Which account is being used for each sale line
## Module Behavior
This module splits income journal entries by payment method. For example, if you have a sale with:
- Total: 100,000
- Payment: 60,000 Cash + 40,000 Bank
Instead of one income line of 100,000, you'll get:
- Income (Cash): 60,000
- Income (Bank): 40,000
This allows you to track income separately by payment method for better financial reporting.

102
README.md
View File

@ -1,51 +1,51 @@
# Split Pendapatan Payment
This module modifies Point of Sale transactions to split income/pendapatan journal entries per payment method used in the transaction.
## Overview
In standard Odoo Point of Sale, all sales income is recorded in a single journal entry line regardless of the payment methods used. This module changes that behavior by creating separate income lines for each payment method used in the transactions.
For example, if a session has sales paid with both cash and credit card, instead of creating one income line for all sales, the module will create separate income lines:
- Sales - Cash
- Sales - Credit Card
## Features
- Splits income journal entries by payment method
- Maintains proper accounting reconciliation
- Works with all payment methods (cash, bank transfers, credit cards, etc.)
- Preserves tax calculations and reporting
- Allows linking specific income accounts to payment methods
## Configuration
### Payment Method Configuration
1. Go to Point of Sale > Configuration > Payment Methods
2. Edit a payment method
3. In the Accounting section, set the "Income Account" field to specify which account should be used for sales paid with this method
4. If left empty, the default product income account will be used
## How it works
The module inherits from the `pos.session` model and overrides two key methods:
1. `_accumulate_amounts()` - Modifies how sales amounts are accumulated to include payment method information and use specific income accounts
2. `_get_sale_vals()` - Customizes the journal entry line descriptions to include payment method names
The module also extends the `pos.payment.method` model to add an "Income Account" field that allows you to specify which account should be used for sales paid with each payment method.
## Installation
1. Place the module in your Odoo addons directory
2. Update the Apps list in Odoo
3. Install the "Split Pendapatan Payment" module
## Compatibility
This module is designed for Odoo 17.0 and is compatible with the standard Point of Sale module.
## Limitations
- Only affects non-invoiced POS orders (invoiced orders follow standard Odoo accounting)
- May require adjustments if used with other POS customization modules that also modify journal entries
# Split Pendapatan Payment
This module modifies Point of Sale transactions to split income/pendapatan journal entries per payment method used in the transaction.
## Overview
In standard Odoo Point of Sale, all sales income is recorded in a single journal entry line regardless of the payment methods used. This module changes that behavior by creating separate income lines for each payment method used in the transactions.
For example, if a session has sales paid with both cash and credit card, instead of creating one income line for all sales, the module will create separate income lines:
- Sales - Cash
- Sales - Credit Card
## Features
- Splits income journal entries by payment method
- Maintains proper accounting reconciliation
- Works with all payment methods (cash, bank transfers, credit cards, etc.)
- Preserves tax calculations and reporting
- Allows linking specific income accounts to payment methods
## Configuration
### Payment Method Configuration
1. Go to Point of Sale > Configuration > Payment Methods
2. Edit a payment method
3. In the Accounting section, set the "Income Account" field to specify which account should be used for sales paid with this method
4. If left empty, the default product income account will be used
## How it works
The module inherits from the `pos.session` model and overrides two key methods:
1. `_accumulate_amounts()` - Modifies how sales amounts are accumulated to include payment method information and use specific income accounts
2. `_get_sale_vals()` - Customizes the journal entry line descriptions to include payment method names
The module also extends the `pos.payment.method` model to add an "Income Account" field that allows you to specify which account should be used for sales paid with each payment method.
## Installation
1. Place the module in your Odoo addons directory
2. Update the Apps list in Odoo
3. Install the "Split Pendapatan Payment" module
## Compatibility
This module is designed for Odoo 17.0 and is compatible with the standard Point of Sale module.
## Limitations
- Only affects non-invoiced POS orders (invoiced orders follow standard Odoo accounting)
- May require adjustments if used with other POS customization modules that also modify journal entries

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
# -*- coding: utf-8 -*-
from . import models

View File

@ -1,37 +1,37 @@
# -*- coding: utf-8 -*-
{
'name': "Split Pendapatan Payment",
'summary': "Split income journal entries per payment method in Point of Sale transactions",
'description': """
This module modifies Point of Sale transactions to split income/pendapatan
journal entries per payment method used in the transaction. Instead of
creating a single income line for all sales, separate income lines are
created for each payment method.
""",
'author': "Suherdy Yacob",
# Categories can be used to filter modules in modules listing
'category': 'Point of Sale',
'version': '17.0.1.0.0',
# any module necessary for this one to work correctly
'depends': ['point_of_sale', 'pos_discount'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/pos_session_views.xml',
'views/pos_payment_method_views.xml',
],
# only loaded in demonstration mode
'demo': [
# 'demo/demo.xml',
],
'installable': True,
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}
# -*- coding: utf-8 -*-
{
'name': "Split Pendapatan Payment",
'summary': "Split income journal entries per payment method in Point of Sale transactions",
'description': """
This module modifies Point of Sale transactions to split income/pendapatan
journal entries per payment method used in the transaction. Instead of
creating a single income line for all sales, separate income lines are
created for each payment method.
""",
'author': "Suherdy Yacob",
# Categories can be used to filter modules in modules listing
'category': 'Point of Sale',
'version': '17.0.1.0.0',
# any module necessary for this one to work correctly
'depends': ['point_of_sale', 'pos_discount'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/pos_session_views.xml',
'views/pos_payment_method_views.xml',
],
# only loaded in demonstration mode
'demo': [
# 'demo/demo.xml',
],
'installable': True,
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
from . import pos_session
from . import pos_payment_method
# -*- coding: utf-8 -*-
from . import pos_session
from . import pos_payment_method

View File

@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class PosPaymentMethod(models.Model):
_inherit = 'pos.payment.method'
income_account_id = fields.Many2one(
'account.account',
string='Income Account',
domain=[('deprecated', '=', False)],
help='Account used for income lines when splitting by payment method. '
'If empty, the default income account from the product will be used.')
discount_account_id = fields.Many2one(
'account.account',
string='Discount Account',
domain=[('deprecated', '=', False)],
help='Account used for discount product lines when splitting by payment method. '
'If empty, the default discount account from the product will be used.')
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class PosPaymentMethod(models.Model):
_inherit = 'pos.payment.method'
income_account_id = fields.Many2one(
'account.account',
string='Income Account',
domain=[('deprecated', '=', False)],
help='Account used for income lines when splitting by payment method. '
'If empty, the default income account from the product will be used.')
discount_account_id = fields.Many2one(
'account.account',
string='Discount Account',
domain=[('deprecated', '=', False)],
help='Account used for discount product lines when splitting by payment method. '
'If empty, the default discount account from the product will be used.')

View File

@ -1,155 +1,280 @@
# -*- coding: utf-8 -*-
from collections import defaultdict
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tools import float_is_zero, float_compare
class PosSession(models.Model):
_inherit = 'pos.session'
def _accumulate_amounts(self, data):
# Call the original method to get all the standard accumulations
data = super(PosSession, self)._accumulate_amounts(data)
# Get all orders in this session
closed_orders = self._get_closed_orders()
# If no orders, return original data
if not closed_orders:
return data
# Get the original sales data
sales = data.get('sales', {})
if not sales:
return data
# Create new sales data structure split by payment method
split_sales = defaultdict(lambda: {'amount': 0.0, 'amount_converted': 0.0, 'tax_amount': 0.0})
# Get discount product ID from config
discount_product_id = self.config_id.discount_product_id.id if self.config_id.discount_product_id else None
# For each sale entry, we need to distribute it across payment methods
for sale_key, sale_amounts in sales.items():
# Skip if this is a tax key (we only want to split actual sales)
if len(sale_key) < 4: # Not a standard sales key
continue
total_amount = sale_amounts['amount']
total_amount_converted = sale_amounts['amount_converted']
tax_amount = sale_amounts.get('tax_amount', 0.0) # Get tax amount if it exists
if float_is_zero(total_amount, precision_rounding=self.currency_id.rounding):
continue
# Distribute this sales amount across all orders based on their payment methods
total_payment_amount = sum(sum(payment.amount for payment in order.payment_ids) for order in closed_orders)
if float_is_zero(total_payment_amount, precision_rounding=self.currency_id.rounding):
continue
# Distribute the sales amount across all orders proportionally to their payments
for order in closed_orders:
if order.is_invoiced:
continue # Skip invoiced orders
order_payments = order.payment_ids
order_payment_total = sum(payment.amount for payment in order_payments)
if float_is_zero(order_payment_total, precision_rounding=order.currency_id.rounding):
continue
# For each payment in this order, create a split sales entry
for payment in order_payments:
# Calculate the proportion of this payment relative to all payments
payment_proportion = payment.amount / total_payment_amount
payment_amount = total_amount * payment_proportion
payment_amount_converted = total_amount_converted * payment_proportion
payment_tax_amount = tax_amount * payment_proportion
if float_is_zero(payment_amount, precision_rounding=self.currency_id.rounding):
continue
# Determine the account to use based on whether this is a discount product
income_account_id = sale_key[0] # default account
# Check if this sale key corresponds to a discount product
# We need to check if the account in the sale_key matches the discount product account
if discount_product_id:
# Get the discount product's income account
discount_product = self.env['product.product'].browse(discount_product_id)
discount_account = discount_product._get_product_accounts()['income']
if discount_account and discount_account.id == sale_key[0]:
# This is a discount product, use discount account if configured
if payment.payment_method_id.discount_account_id:
income_account_id = payment.payment_method_id.discount_account_id.id
elif payment.payment_method_id.income_account_id:
# This is a regular product, use income account if configured
income_account_id = payment.payment_method_id.income_account_id.id
# Ensure we have a valid account ID
if not income_account_id:
continue
# Create a new key that includes the payment method
new_sale_key = (
# account (use payment method account if specified)
income_account_id,
# sign (same as original)
sale_key[1],
# payment method
payment.payment_method_id.id,
# for taxes (same as original)
sale_key[2],
# base tags (same as original)
sale_key[3],
)
# Update the split sales data
split_sales[new_sale_key]['amount'] += payment_amount
split_sales[new_sale_key]['amount_converted'] += payment_amount_converted
split_sales[new_sale_key]['tax_amount'] += payment_tax_amount
# Replace the original sales data with our split sales data
data['sales'] = split_sales
return data
def _get_sale_vals(self, key, amount, amount_converted):
""" Override to add payment method information to the sales line description """
# Check if this key includes payment method information
if len(key) >= 5 and isinstance(key[2], int): # Has payment method ID
account_id, sign, payment_method_id, tax_keys, base_tag_ids = key
# Try to get the payment method name
try:
payment_method = self.env['pos.payment.method'].browse(payment_method_id)
payment_method_name = payment_method.name
except:
payment_method_name = "Unknown Payment"
else:
# Original format
account_id, sign, tax_keys, base_tag_ids = key
payment_method_name = None
tax_ids = set(tax[0] for tax in tax_keys) if tax_keys else set()
applied_taxes = self.env['account.tax'].browse(tax_ids)
title = _('Sales') if sign == 1 else _('Refund')
# Create name with payment method information
if payment_method_name:
name = _('%s - %s', title, payment_method_name)
if applied_taxes:
name = _('%s with %s - %s', title, ', '.join([tax.name for tax in applied_taxes]), payment_method_name)
else:
name = _('%s untaxed', title)
if applied_taxes:
name = _('%s with %s', title, ', '.join([tax.name for tax in applied_taxes]))
partial_vals = {
'name': name,
'account_id': account_id,
'move_id': self.move_id.id,
'tax_ids': [(6, 0, tax_ids)],
'tax_tag_ids': [(6, 0, base_tag_ids)] if base_tag_ids else [],
}
return self._credit_amounts(partial_vals, amount, amount_converted)
# -*- coding: utf-8 -*-
from collections import defaultdict
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tools import float_is_zero, float_compare
import logging
_logger = logging.getLogger(__name__)
class PosSession(models.Model):
_inherit = 'pos.session'
def _prepare_line(self, order_line):
"""Override to allow products without income accounts when payment methods have them configured"""
def get_income_account(order_line):
product = order_line.product_id
income_account = product.with_company(order_line.company_id)._get_product_accounts()['income'] or self.config_id.journal_id.default_account_id
# NEW: If no income account is found on the product, check if payment methods have income accounts configured
if not income_account:
# First look at payment methods actually used on the order
payment_methods_used = order_line.order_id.payment_ids.mapped('payment_method_id').filtered('income_account_id')
payment_methods_with_income = payment_methods_used or self.payment_method_ids.filtered('income_account_id')
payment_income_account = payment_methods_with_income[:1].income_account_id
if payment_income_account:
_logger.info(
"Product '%s' (id:%s) has no income account. "
"Using payment method '%s' income account %s as fallback.",
product.name, product.id,
payment_methods_with_income[:1].name,
payment_income_account.display_name
)
income_account = payment_income_account
else:
_logger.warning(
"Product '%s' (id:%s) has no income account and no payment methods provide an income account.",
product.name, product.id
)
raise UserError(_('Please define income account for this product: "%s" (id:%d).\n'
'Or configure the Income Account in your payment methods.',
product.name, product.id))
return order_line.order_id.fiscal_position_id.map_account(income_account)
company_domain = self.env['account.tax']._check_company_domain(order_line.order_id.company_id)
tax_ids = order_line.tax_ids_after_fiscal_position.filtered_domain(company_domain)
sign = -1 if order_line.qty >= 0 else 1
price = sign * order_line.price_unit * (1 - (order_line.discount or 0.0) / 100.0)
check_refund = lambda x: x.qty * x.price_unit < 0
is_refund = check_refund(order_line)
tax_data = tax_ids.compute_all(price_unit=price, quantity=abs(order_line.qty), currency=self.currency_id, is_refund=is_refund, fixed_multiplicator=sign, include_caba_tags=True)
date_order = order_line.order_id.date_order
taxes = [{'date_order': date_order, **tax} for tax in tax_data['taxes']]
return {
'date_order': order_line.order_id.date_order,
'income_account_id': get_income_account(order_line).id,
'amount': order_line.price_subtotal,
'taxes': taxes,
'base_tags': tuple(tax_data['base_tags']),
}
def _accumulate_amounts(self, data):
"""Distribute sales amounts per payment method while respecting discount accounts."""
data = super(PosSession, self)._accumulate_amounts(data)
closed_orders = self._get_closed_orders()
if not closed_orders:
return data
sales = data.get('sales', {})
if not sales:
return data
discount_product_id = self.config_id.discount_product_id.id if self.config_id.discount_product_id else None
# Build per-order breakdown of sales keyed by (account, sign, tax tuple, tags)
order_payment_totals = {}
order_sales_breakdown = {}
for order in closed_orders:
order_payment_totals[order.id] = sum(payment.amount for payment in order.payment_ids)
if order.is_invoiced:
continue
sale_map = {}
for order_line in order.lines:
line_vals = self._prepare_line(order_line)
sale_key = (
line_vals['income_account_id'],
-1 if line_vals['amount'] < 0 else 1,
tuple((tax['id'], tax['account_id'], tax['tax_repartition_line_id']) for tax in line_vals['taxes']),
line_vals['base_tags'],
)
entry = sale_map.setdefault(sale_key, {
'amount': 0.0,
'amount_converted': 0.0,
'tax_amount': 0.0,
'regular_amount': 0.0,
'regular_amount_converted': 0.0,
'regular_tax_amount': 0.0,
'discount_amount': 0.0,
'discount_amount_converted': 0.0,
'discount_tax_amount': 0.0,
})
prev_amount = entry['amount']
prev_amount_converted = entry['amount_converted']
updated_amounts = self._update_amounts(
{'amount': prev_amount, 'amount_converted': prev_amount_converted},
{'amount': line_vals['amount']},
order.date_order,
round=False,
)
line_amount_converted = updated_amounts['amount_converted'] - prev_amount_converted
line_tax_amount = sum(tax['amount'] for tax in line_vals['taxes'])
entry['amount'] = updated_amounts['amount']
entry['amount_converted'] = updated_amounts['amount_converted']
entry['tax_amount'] += line_tax_amount
is_reward_line = bool(getattr(order_line, 'is_reward_line', False))
has_reward = bool(getattr(order_line, 'reward_id', False))
is_discount_product = bool(discount_product_id and order_line.product_id.id == discount_product_id)
is_discount_line = is_reward_line or has_reward or is_discount_product
if is_discount_line:
entry['discount_amount'] += line_vals['amount']
entry['discount_amount_converted'] += line_amount_converted
entry['discount_tax_amount'] += line_tax_amount
else:
entry['regular_amount'] += line_vals['amount']
entry['regular_amount_converted'] += line_amount_converted
entry['regular_tax_amount'] += line_tax_amount
if sale_map:
order_sales_breakdown[order.id] = sale_map
split_sales = defaultdict(lambda: {'amount': 0.0, 'amount_converted': 0.0, 'tax_amount': 0.0})
for sale_key, sale_amounts in sales.items():
if len(sale_key) < 4:
continue
total_amount = sale_amounts['amount']
if float_is_zero(total_amount, precision_rounding=self.currency_id.rounding):
continue
for order in closed_orders:
if order.is_invoiced:
continue
order_sale_map = order_sales_breakdown.get(order.id)
if not order_sale_map:
continue
order_sale_amounts = order_sale_map.get(sale_key)
if not order_sale_amounts:
continue
order_payment_total = order_payment_totals.get(order.id, 0.0)
if float_is_zero(order_payment_total, precision_rounding=order.currency_id.rounding):
continue
order_amount = order_sale_amounts['amount']
order_amount_converted = order_sale_amounts['amount_converted']
order_tax_amount = order_sale_amounts['tax_amount']
if float_is_zero(order_amount, precision_rounding=self.currency_id.rounding):
continue
for payment in order.payment_ids:
if float_is_zero(payment.amount, precision_rounding=order.currency_id.rounding):
continue
payment_proportion = payment.amount / order_payment_total
net_amount = order_amount * payment_proportion
net_amount_converted = order_amount_converted * payment_proportion
net_tax_amount = order_tax_amount * payment_proportion
regular_part_amount = order_sale_amounts['regular_amount'] * payment_proportion
discount_part_amount = order_sale_amounts['discount_amount'] * payment_proportion
regular_part_amount_converted = order_sale_amounts['regular_amount_converted'] * payment_proportion
discount_part_amount_converted = order_sale_amounts['discount_amount_converted'] * payment_proportion
regular_part_tax = order_sale_amounts['regular_tax_amount'] * payment_proportion
discount_part_tax = order_sale_amounts['discount_tax_amount'] * payment_proportion
residual_amount = net_amount - (regular_part_amount + discount_part_amount)
residual_amount_converted = net_amount_converted - (regular_part_amount_converted + discount_part_amount_converted)
residual_tax = net_tax_amount - (regular_part_tax + discount_part_tax)
if abs(regular_part_amount) >= abs(discount_part_amount):
regular_part_amount += residual_amount
regular_part_amount_converted += residual_amount_converted
regular_part_tax += residual_tax
else:
discount_part_amount += residual_amount
discount_part_amount_converted += residual_amount_converted
discount_part_tax += residual_tax
def add_split_entry(part_amount, part_amount_converted, part_tax_amount, is_discount_part):
if float_is_zero(part_amount, precision_rounding=self.currency_id.rounding):
return
target_account_id = sale_key[0]
if is_discount_part:
if payment.payment_method_id.discount_account_id:
target_account_id = payment.payment_method_id.discount_account_id.id
elif payment.payment_method_id.income_account_id:
target_account_id = payment.payment_method_id.income_account_id.id
_logger.warning(
"Payment method %s has no discount account configured; using income account instead for discount lines.",
payment.payment_method_id.name,
)
else:
if payment.payment_method_id.income_account_id:
target_account_id = payment.payment_method_id.income_account_id.id
if not target_account_id:
raise UserError(_(
'No income account found for payment method "%s".\n'
'Please configure the Income Account in the payment method settings, '
'or ensure the product has a valid income account defined.'
) % payment.payment_method_id.name)
new_sale_key = (
target_account_id,
sale_key[1],
payment.payment_method_id.id,
sale_key[2],
sale_key[3],
)
split_sales[new_sale_key]['amount'] += part_amount
split_sales[new_sale_key]['amount_converted'] += part_amount_converted
split_sales[new_sale_key]['tax_amount'] += part_tax_amount
add_split_entry(regular_part_amount, regular_part_amount_converted, regular_part_tax, False)
add_split_entry(discount_part_amount, discount_part_amount_converted, discount_part_tax, True)
data['sales'] = split_sales
return data
def _get_sale_vals(self, key, amount, amount_converted):
""" Override to add payment method information to the sales line description """
# Check if this key includes payment method information
if len(key) >= 5 and isinstance(key[2], int): # Has payment method ID
account_id, sign, payment_method_id, tax_keys, base_tag_ids = key
# Try to get the payment method name
try:
payment_method = self.env['pos.payment.method'].browse(payment_method_id)
payment_method_name = payment_method.name
except:
payment_method_name = "Unknown Payment"
else:
# Original format
account_id, sign, tax_keys, base_tag_ids = key
payment_method_name = None
tax_ids = set(tax[0] for tax in tax_keys) if tax_keys else set()
applied_taxes = self.env['account.tax'].browse(tax_ids)
title = _('Sales') if sign == 1 else _('Refund')
# Create name with payment method information
if payment_method_name:
name = _('%s - %s', title, payment_method_name)
if applied_taxes:
name = _('%s with %s - %s', title, ', '.join([tax.name for tax in applied_taxes]), payment_method_name)
else:
name = _('%s untaxed', title)
if applied_taxes:
name = _('%s with %s', title, ', '.join([tax.name for tax in applied_taxes]))
partial_vals = {
'name': name,
'account_id': account_id,
'move_id': self.move_id.id,
'tax_ids': [(6, 0, tax_ids)],
'tax_tag_ids': [(6, 0, base_tag_ids)] if base_tag_ids else [],
}
return self._credit_amounts(partial_vals, amount, amount_converted)

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_payment_method_form_inherit" model="ir.ui.view">
<field name="name">pos.payment.method.form.inherit</field>
<field name="model">pos.payment.method</field>
<field name="inherit_id" ref="point_of_sale.pos_payment_method_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='Payment methods']/group" position="inside">
<field name="income_account_id" domain="[('deprecated', '=', False)]"/>
<field name="discount_account_id" domain="[('deprecated', '=', False)]"/>
</xpath>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_payment_method_form_inherit" model="ir.ui.view">
<field name="name">pos.payment.method.form.inherit</field>
<field name="model">pos.payment.method</field>
<field name="inherit_id" ref="point_of_sale.pos_payment_method_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='Payment methods']/group" position="inside">
<field name="income_account_id" domain="[('deprecated', '=', False)]"/>
<field name="discount_account_id" domain="[('deprecated', '=', False)]"/>
</xpath>
</field>
</record>
</odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- No additional views needed for this module -->
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- No additional views needed for this module -->
</odoo>