first commit
This commit is contained in:
commit
327712ef46
51
README.md
Normal file
51
README.md
Normal file
@ -0,0 +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
|
||||||
3
__init__.py
Normal file
3
__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
38
__manifest__.py
Normal file
38
__manifest__.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- 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': "Your Company",
|
||||||
|
'website': "https://www.yourcompany.com",
|
||||||
|
|
||||||
|
# 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'],
|
||||||
|
|
||||||
|
# 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',
|
||||||
|
}
|
||||||
4
models/__init__.py
Normal file
4
models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import pos_session
|
||||||
|
from . import pos_payment_method
|
||||||
13
models/pos_payment_method.py
Normal file
13
models/pos_payment_method.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# -*- 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.')
|
||||||
140
models/pos_session.py
Normal file
140
models/pos_session.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# -*- 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})
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Use the payment method's income account if specified, otherwise use original account
|
||||||
|
income_account_id = sale_key[0] # default account
|
||||||
|
if payment.payment_method_id.income_account_id and payment.payment_method_id.income_account_id.id:
|
||||||
|
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)
|
||||||
13
views/pos_payment_method_views.xml
Normal file
13
views/pos_payment_method_views.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?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)]"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
4
views/pos_session_views.xml
Normal file
4
views/pos_session_views.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- No additional views needed for this module -->
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user