Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions pos_financial_surcharge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Módulo: POS - Recargo Financiero por Tarjeta

Este módulo permite aplicar recargos financieros personalizados según el plan de cuotas elegido por el cliente al pagar con tarjeta en el Punto de Venta (POS) de Odoo 18. Es especialmente útil para operaciones con tarjetas de crédito donde el comercio asume un interés bancario y desea trasladarlo al consumidor.

## Características Principales

- Permite asociar productos de recargo a métodos de pago POS.
- Soporte para múltiples tarjetas y planes de cuotas (cuotas, coeficiente de recargo, descuentos bancarios).
- Muestra un popup en el POS para seleccionar tarjeta, lote, cupón y plan de financiación.
- Calcula y agrega automáticamente el recargo como una línea adicional en el pedido.
- Configuración visual por método de pago para definir qué tarjetas están permitidas.
- Validaciones de productos sin impuestos en los recargos.

---

## Instalación

1. Clonar este módulo en el directorio de addons de tu instancia de Odoo:
```bash
git clone https://github.com/filoquin/pos_payment.git
```

2. Activar el modo desarrollador en Odoo y habilitar el módulo.

---

## Configuración

### 1. Productos de Recargo
- Crear un producto tipo "Servicio" con `Disponible en POS` y sin impuestos y para argentina IVA 0% si se factura.
- Este producto se usará para cargar el importe adicional del plan de financiación.

### 2. Métodos de Pago POS
- Ir a **Punto de Venta → Configuración → Métodos de pago**.
- Seleccionar un método de tipo `Terminal` e integrar con `Card financial surcharge`.
- Asignar:
- **Producto de recargo financiero**
- **Tarjetas permitidas en POS**

### 3. Tarjetas y Cuotas
- Crear tarjetas en **Contabilidad → Configuración → Tarjetas**.
- Asociar planes de cuotas a cada tarjeta con:
- Cantidad de cuotas
- Coeficiente de recargo
- Descuento bancario (opcional)

---

## Uso en el Punto de Venta

1. Al seleccionar un método de pago configurado con recargo, se abre automáticamente un popup.
2. El usuario debe seleccionar:
- Tarjeta
- Plan de cuotas
3. Se calcula el total ajustado y se agrega una línea de recargo si corresponde.
4. Se guarda una nota de cliente con los datos de la operación.

---

## Detalles Técnicos

- **Modelos Extendidos:**
- `pos.payment.method`: Añade campo `bank_charge_prod_id` y `available_cards_ids`.
- `account.card`: Filtrado en POS según método de pago.
- `account.card.installment`: Cargado como dependencia POS.
- **Frontend:**
- Reemplaza el flujo de pago estándar con un `PaymentInterface` personalizado.
- Incluye popup interactivo con OWL.
- **Integración POS:**
- Declaración con `register_payment_method("financial_surcharge", ...)`.

---

## Compatibilidad

- Odoo 18 (Tested)
- Compatible con POS Web y POS Touch
- Modo multi-tienda compatible

---

## Créditos

Desarrollado por: Martín Quinteros (Filoquin), Francisco Sulé.
Especialista funcional y técnico en Odoo para Argentina 🇦🇷

---


2 changes: 2 additions & 0 deletions pos_financial_surcharge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizards
21 changes: 21 additions & 0 deletions pos_financial_surcharge/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
'name': 'Pos Financial Surchage',
'version': "18.0.1.0.0",
'category': 'Sales/Point of Sale',
'sequence': 6,
'summary': 'Add pos finanacial surcharge',
'data': [
'views/card_installment_view.xml',
'views/pos_payment_method.xml',
'wizards/res_config_settings_views.xml',
],
'depends': ['point_of_sale', 'card_installment'],
'installable': True,
'assets': {
'point_of_sale._assets_pos': [
'pos_financial_surcharge/static/src/**/*',
'pos_financial_surcharge/static/src/**/**/*',
],
},
'license': 'LGPL-3',
}
5 changes: 5 additions & 0 deletions pos_financial_surcharge/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import account_card
from . import account_card_installment
from . import pos_session
from . import pos_payment_method
from . import res_company
30 changes: 30 additions & 0 deletions pos_financial_surcharge/models/account_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from odoo import models, api, fields


class AccountCard(models.Model):
_inherit = "account.card"

available_in_pos = fields.Boolean(
string='Available in POS',
help='Check if you want this card can be used in the Point of Sale.',
default=False
)

@api.model
def _load_pos_data_domain(self, data):
return self.env['account.card']._check_company_domain(data['pos.config']['data'][0]['company_id'])
#+ [('available_in_pos', '=', True)]

@api.model
def _load_pos_data_fields(self, config_id):
return [
'id', 'name', 'installment_ids'
]

def _load_pos_data(self, data):
domain = self._load_pos_data_domain(data)
fields = self._load_pos_data_fields(data['pos.config']['data'][0]['id'])
return {
'data': self.search_read(domain, fields, load=False) if domain is not False else [],
'fields': fields,
}
24 changes: 24 additions & 0 deletions pos_financial_surcharge/models/account_card_installment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from odoo import models, api


class AccountCard(models.Model):
_inherit = "account.card.installment"

@api.model
def _load_pos_data_domain(self, data):
#return self.env['account.card']._check_company_domain(data['pos.config']['data'][0]['company_id'])
return []

@api.model
def _load_pos_data_fields(self, config_id):
return [
'id', 'card_id', 'name', 'divisor', 'installment', 'surcharge_coefficient', 'bank_discount'
]

def _load_pos_data(self, data):
domain = self._load_pos_data_domain(data)
fields = self._load_pos_data_fields(data['pos.config']['data'][0]['id'])
return {
'data': self.search_read(domain, fields, load=False) if domain is not False else [],
'fields': fields,
}
21 changes: 21 additions & 0 deletions pos_financial_surcharge/models/pos_payment_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import logging

from odoo import models, fields, api

_logger = logging.getLogger(__name__)


class PosPaymentMethod(models.Model):
_inherit = 'pos.payment.method'

available_card_ids = fields.Many2many(
"account.card", string="Cards",
)


def _get_payment_terminal_selection(self):
return super()._get_payment_terminal_selection() + [('financial_surcharge', 'Card financial surcharge')]

@api.model
def _load_pos_data_fields(self, config_id):
return super()._load_pos_data_fields(config_id) + ['available_card_ids']
10 changes: 10 additions & 0 deletions pos_financial_surcharge/models/pos_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import api, fields, models, _, Command
from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError


class PosSession(models.Model):
_inherit = 'pos.session'

@api.model
def _load_pos_data_models(self, config_id):
return super()._load_pos_data_models(config_id) + ['account.card', 'account.card.installment']
9 changes: 9 additions & 0 deletions pos_financial_surcharge/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from odoo import api, models


class ResCompany(models.Model):
_inherit = "res.company"

@api.model
def _load_pos_data_fields(self, config_id):
return super()._load_pos_data_fields(config_id) + ['product_surcharge_id']
77 changes: 77 additions & 0 deletions pos_financial_surcharge/static/src/app/financial_surcharge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/** @odoo-module */
import { _t } from "@web/core/l10n/translation";
import { PaymentInterface } from "@point_of_sale/app/payment/payment_interface";
import { ask } from "@point_of_sale/app/store/make_awaitable_dialog";
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { FinancialSurchargePopup } from "@pos_financial_surcharge/app/financial_surcharge_popup/financial_surcharge_popup";


export class FinancialSurcharge extends PaymentInterface {

async _get_cards(){
const res = []
const installment_ids = this.pos.models['account.card.installment'].getAll()
const available_card_ids = this.payment_method_id.available_card_ids.map((card)=> card.id);
for (const card of this.pos.models['account.card'].getAll().filter((card) =>{
return available_card_ids.includes(card.id);
})
) {
res.push({
id: card.id,
name: card.name,
installments: installment_ids.filter((installment) => {
return installment.card_id.id == card.id;
}).map((installment) => {
return {
id: installment.id,
name: installment.name,
divisor: installment.divisor,
installment: installment.installment,
surcharge_coefficient: installment.surcharge_coefficient,
bank_discount: installment.bank_discount,
};
}),
})
}
return res;
}
async send_payment_request(cid) {
await super.send_payment_request(...arguments);
const line = this.pos.get_order().get_selected_paymentline();
try {
// During payment creation, user can't cancel the payment intent
line.set_payment_status("waitingCapture");
return await ask(
this.env.services.dialog,
{
title: 'Select payment intallment ',
line: line,
cards: await this._get_cards(),
order: line.pos_order_id,
pos: this.pos,
},
{},
FinancialSurchargePopup
).then((result) => {
return result;
});

} catch (error) {
console.error(error);
this._showMsg('error', "System error");
return false;
}
}

async send_payment_cancel(order, cid) {
await super.send_payment_cancel(order, cid);
return true;
}
// private methods
_showMsg(msg, title) {
this.env.services.dialog.add(AlertDialog, {
title: "Error " + title,
body: msg,
});
}
}
Loading