Compare commits

..

4 Commits

12 changed files with 422 additions and 617 deletions

273
.gitignore vendored
View File

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

View File

@ -1,80 +0,0 @@
# 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 # Split Pendapatan Payment
This module modifies Point of Sale transactions to split income/pendapatan journal entries per payment method used in the transaction. This module modifies Point of Sale transactions to split income/pendapatan journal entries per payment method used in the transaction.
## Overview ## 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. 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: 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 - Cash
- Sales - Credit Card - Sales - Credit Card
## Features ## Features
- Splits income journal entries by payment method - Splits income journal entries by payment method
- Maintains proper accounting reconciliation - Maintains proper accounting reconciliation
- Works with all payment methods (cash, bank transfers, credit cards, etc.) - Works with all payment methods (cash, bank transfers, credit cards, etc.)
- Preserves tax calculations and reporting - Preserves tax calculations and reporting
- Allows linking specific income accounts to payment methods - Allows linking specific income accounts to payment methods
## Configuration ## Configuration
### Payment Method Configuration ### Payment Method Configuration
1. Go to Point of Sale > Configuration > Payment Methods 1. Go to Point of Sale > Configuration > Payment Methods
2. Edit a payment method 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 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 4. If left empty, the default product income account will be used
## How it works ## How it works
The module inherits from the `pos.session` model and overrides two key methods: 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 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 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. 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 ## Installation
1. Place the module in your Odoo addons directory 1. Place the module in your Odoo addons directory
2. Update the Apps list in Odoo 2. Update the Apps list in Odoo
3. Install the "Split Pendapatan Payment" module 3. Install the "Split Pendapatan Payment" module
## Compatibility ## Compatibility
This module is designed for Odoo 17.0 and is compatible with the standard Point of Sale module. This module is designed for Odoo 17.0 and is compatible with the standard Point of Sale module.
## Limitations ## Limitations
- Only affects non-invoiced POS orders (invoiced orders follow standard Odoo accounting) - 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 - May require adjustments if used with other POS customization modules that also modify journal entries

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -1,264 +1,155 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from collections import defaultdict from collections import defaultdict
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import float_is_zero, float_compare from odoo.tools import float_is_zero, float_compare
class PosSession(models.Model): class PosSession(models.Model):
_inherit = 'pos.session' _inherit = 'pos.session'
def _prepare_line(self, order_line): def _accumulate_amounts(self, data):
"""Override to allow products without income accounts when payment methods have them configured""" # Call the original method to get all the standard accumulations
def get_income_account(order_line): data = super(PosSession, self)._accumulate_amounts(data)
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 # Get all orders in this session
closed_orders = self._get_closed_orders()
# NEW: If no income account is found on the product, check if payment methods have income accounts configured
if not income_account: # If no orders, return original data
# First look at payment methods actually used on the order if not closed_orders:
payment_methods_used = order_line.order_id.payment_ids.mapped('payment_method_id').filtered('income_account_id') return data
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 # Get the original sales data
sales = data.get('sales', {})
if payment_income_account: if not sales:
income_account = payment_income_account return data
else:
raise UserError(_('Please define income account for this product: "%s" (id:%d).\n' # Create new sales data structure split by payment method
'Or configure the Income Account in your payment methods.', split_sales = defaultdict(lambda: {'amount': 0.0, 'amount_converted': 0.0, 'tax_amount': 0.0})
product.name, product.id))
# Get discount product ID from config
return order_line.order_id.fiscal_position_id.map_account(income_account) discount_product_id = self.config_id.discount_product_id.id if self.config_id.discount_product_id else None
company_domain = self.env['account.tax']._check_company_domain(order_line.order_id.company_id) # For each sale entry, we need to distribute it across payment methods
tax_ids = order_line.tax_ids_after_fiscal_position.filtered_domain(company_domain) for sale_key, sale_amounts in sales.items():
sign = -1 if order_line.qty >= 0 else 1 # Skip if this is a tax key (we only want to split actual sales)
price = sign * order_line.price_unit * (1 - (order_line.discount or 0.0) / 100.0) if len(sale_key) < 4: # Not a standard sales key
check_refund = lambda x: x.qty * x.price_unit < 0 continue
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) total_amount = sale_amounts['amount']
date_order = order_line.order_id.date_order total_amount_converted = sale_amounts['amount_converted']
taxes = [{'date_order': date_order, **tax} for tax in tax_data['taxes']] tax_amount = sale_amounts.get('tax_amount', 0.0) # Get tax amount if it exists
return {
'date_order': order_line.order_id.date_order, if float_is_zero(total_amount, precision_rounding=self.currency_id.rounding):
'income_account_id': get_income_account(order_line).id, continue
'amount': order_line.price_subtotal,
'taxes': taxes, # Distribute this sales amount across all orders based on their payment methods
'base_tags': tuple(tax_data['base_tags']), 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):
def _accumulate_amounts(self, data): continue
"""Distribute sales amounts per payment method while respecting discount accounts."""
data = super(PosSession, self)._accumulate_amounts(data) # Distribute the sales amount across all orders proportionally to their payments
for order in closed_orders:
closed_orders = self._get_closed_orders() if order.is_invoiced:
if not closed_orders: continue # Skip invoiced orders
return data
order_payments = order.payment_ids
sales = data.get('sales', {}) order_payment_total = sum(payment.amount for payment in order_payments)
if not sales:
return data if float_is_zero(order_payment_total, precision_rounding=order.currency_id.rounding):
continue
discount_product_id = self.config_id.discount_product_id.id if self.config_id.discount_product_id else None
# For each payment in this order, create a split sales entry
# Build per-order breakdown of sales keyed by (account, sign, tax tuple, tags) for payment in order_payments:
order_payment_totals = {} # Calculate the proportion of this payment relative to all payments
order_sales_breakdown = {} payment_proportion = payment.amount / total_payment_amount
for order in closed_orders: payment_amount = total_amount * payment_proportion
order_payment_totals[order.id] = sum(payment.amount for payment in order.payment_ids) payment_amount_converted = total_amount_converted * payment_proportion
if order.is_invoiced: payment_tax_amount = tax_amount * payment_proportion
continue
if float_is_zero(payment_amount, precision_rounding=self.currency_id.rounding):
sale_map = {} continue
for order_line in order.lines:
line_vals = self._prepare_line(order_line) # Determine the account to use based on whether this is a discount product
sale_key = ( income_account_id = sale_key[0] # default account
line_vals['income_account_id'],
-1 if line_vals['amount'] < 0 else 1, # Check if this sale key corresponds to a discount product
tuple((tax['id'], tax['account_id'], tax['tax_repartition_line_id']) for tax in line_vals['taxes']), # We need to check if the account in the sale_key matches the discount product account
line_vals['base_tags'], if discount_product_id:
) # Get the discount product's income account
entry = sale_map.setdefault(sale_key, { discount_product = self.env['product.product'].browse(discount_product_id)
'amount': 0.0, discount_account = discount_product._get_product_accounts()['income']
'amount_converted': 0.0, if discount_account and discount_account.id == sale_key[0]:
'tax_amount': 0.0, # This is a discount product, use discount account if configured
'regular_amount': 0.0, if payment.payment_method_id.discount_account_id:
'regular_amount_converted': 0.0, income_account_id = payment.payment_method_id.discount_account_id.id
'regular_tax_amount': 0.0, elif payment.payment_method_id.income_account_id:
'discount_amount': 0.0, # This is a regular product, use income account if configured
'discount_amount_converted': 0.0, income_account_id = payment.payment_method_id.income_account_id.id
'discount_tax_amount': 0.0,
}) # Ensure we have a valid account ID
if not income_account_id:
prev_amount = entry['amount'] continue
prev_amount_converted = entry['amount_converted']
updated_amounts = self._update_amounts( # Create a new key that includes the payment method
{'amount': prev_amount, 'amount_converted': prev_amount_converted}, new_sale_key = (
{'amount': line_vals['amount']}, # account (use payment method account if specified)
order.date_order, income_account_id,
round=False, # sign (same as original)
) sale_key[1],
line_amount_converted = updated_amounts['amount_converted'] - prev_amount_converted # payment method
line_tax_amount = sum(tax['amount'] for tax in line_vals['taxes']) payment.payment_method_id.id,
# for taxes (same as original)
entry['amount'] = updated_amounts['amount'] sale_key[2],
entry['amount_converted'] = updated_amounts['amount_converted'] # base tags (same as original)
entry['tax_amount'] += line_tax_amount sale_key[3],
)
is_reward_line = bool(getattr(order_line, 'is_reward_line', False))
has_reward = bool(getattr(order_line, 'reward_id', False)) # Update the split sales data
is_discount_product = bool(discount_product_id and order_line.product_id.id == discount_product_id) split_sales[new_sale_key]['amount'] += payment_amount
is_discount_line = is_reward_line or has_reward or is_discount_product split_sales[new_sale_key]['amount_converted'] += payment_amount_converted
split_sales[new_sale_key]['tax_amount'] += payment_tax_amount
if is_discount_line:
entry['discount_amount'] += line_vals['amount'] # Replace the original sales data with our split sales data
entry['discount_amount_converted'] += line_amount_converted data['sales'] = split_sales
entry['discount_tax_amount'] += line_tax_amount return data
else:
entry['regular_amount'] += line_vals['amount'] def _get_sale_vals(self, key, amount, amount_converted):
entry['regular_amount_converted'] += line_amount_converted """ Override to add payment method information to the sales line description """
entry['regular_tax_amount'] += line_tax_amount # Check if this key includes payment method information
if len(key) >= 5 and isinstance(key[2], int): # Has payment method ID
if sale_map: account_id, sign, payment_method_id, tax_keys, base_tag_ids = key
order_sales_breakdown[order.id] = sale_map # Try to get the payment method name
try:
split_sales = defaultdict(lambda: {'amount': 0.0, 'amount_converted': 0.0, 'tax_amount': 0.0}) payment_method = self.env['pos.payment.method'].browse(payment_method_id)
payment_method_name = payment_method.name
for sale_key, sale_amounts in sales.items(): except:
if len(sale_key) < 4: payment_method_name = "Unknown Payment"
continue else:
# Original format
total_amount = sale_amounts['amount'] account_id, sign, tax_keys, base_tag_ids = key
if float_is_zero(total_amount, precision_rounding=self.currency_id.rounding): payment_method_name = None
continue
tax_ids = set(tax[0] for tax in tax_keys) if tax_keys else set()
for order in closed_orders: applied_taxes = self.env['account.tax'].browse(tax_ids)
if order.is_invoiced: title = _('Sales') if sign == 1 else _('Refund')
continue
# Create name with payment method information
order_sale_map = order_sales_breakdown.get(order.id) if payment_method_name:
if not order_sale_map: name = _('%s - %s', title, payment_method_name)
continue if applied_taxes:
name = _('%s with %s - %s', title, ', '.join([tax.name for tax in applied_taxes]), payment_method_name)
order_sale_amounts = order_sale_map.get(sale_key) else:
if not order_sale_amounts: name = _('%s untaxed', title)
continue if applied_taxes:
name = _('%s with %s', title, ', '.join([tax.name for tax in applied_taxes]))
order_payment_total = order_payment_totals.get(order.id, 0.0)
#if float_is_zero(order_payment_total, precision_rounding=order.currency_id.rounding): partial_vals = {
# continue 'name': name,
'account_id': account_id,
order_amount = order_sale_amounts['amount'] 'move_id': self.move_id.id,
order_amount_converted = order_sale_amounts['amount_converted'] 'tax_ids': [(6, 0, tax_ids)],
order_tax_amount = order_sale_amounts['tax_amount'] 'tax_tag_ids': [(6, 0, base_tag_ids)] if base_tag_ids else [],
if float_is_zero(order_amount, precision_rounding=self.currency_id.rounding): }
continue return self._credit_amounts(partial_vals, amount, amount_converted)
for payment in order.payment_ids:
#if float_is_zero(payment.amount, precision_rounding=order.currency_id.rounding):
# continue
if float_is_zero(payment.amount, precision_rounding=order.currency_id.rounding):
payment_proportion = 1.0
else:
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
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"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<record id="view_pos_payment_method_form_inherit" model="ir.ui.view"> <record id="view_pos_payment_method_form_inherit" model="ir.ui.view">
<field name="name">pos.payment.method.form.inherit</field> <field name="name">pos.payment.method.form.inherit</field>
<field name="model">pos.payment.method</field> <field name="model">pos.payment.method</field>
<field name="inherit_id" ref="point_of_sale.pos_payment_method_view_form"/> <field name="inherit_id" ref="point_of_sale.pos_payment_method_view_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group[@name='Payment methods']/group" position="inside"> <xpath expr="//group[@name='Payment methods']/group" position="inside">
<field name="income_account_id" domain="[('deprecated', '=', False)]"/> <field name="income_account_id" domain="[('deprecated', '=', False)]"/>
<field name="discount_account_id" domain="[('deprecated', '=', False)]"/> <field name="discount_account_id" domain="[('deprecated', '=', False)]"/>
</xpath> </xpath>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

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