commit 327712ef467bbbe8b44536e20d5add4e96d68563 Author: Suherdy SYC. Yacob Date: Sat Aug 16 11:46:03 2025 +0700 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..31deecd --- /dev/null +++ b/README.md @@ -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 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..cde864b --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..0e32ceb --- /dev/null +++ b/__manifest__.py @@ -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', +} diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..f27bfcb --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import pos_session +from . import pos_payment_method diff --git a/models/pos_payment_method.py b/models/pos_payment_method.py new file mode 100644 index 0000000..6ca9170 --- /dev/null +++ b/models/pos_payment_method.py @@ -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.') diff --git a/models/pos_session.py b/models/pos_session.py new file mode 100644 index 0000000..f09d781 --- /dev/null +++ b/models/pos_session.py @@ -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) diff --git a/views/pos_payment_method_views.xml b/views/pos_payment_method_views.xml new file mode 100644 index 0000000..37c772b --- /dev/null +++ b/views/pos_payment_method_views.xml @@ -0,0 +1,13 @@ + + + + pos.payment.method.form.inherit + pos.payment.method + + + + + + + + diff --git a/views/pos_session_views.xml b/views/pos_session_views.xml new file mode 100644 index 0000000..8b82d66 --- /dev/null +++ b/views/pos_session_views.xml @@ -0,0 +1,4 @@ + + + +