Enric Tobella
7 years ago
committed by
Jordi Ballester Alomar
27 changed files with 889 additions and 0 deletions
-
1.gitignore
-
57account_cash_invoice/README.rst
-
5account_cash_invoice/__init__.py
-
21account_cash_invoice/__manifest__.py
-
4account_cash_invoice/models/__init__.py
-
35account_cash_invoice/models/account_bank_statement_line.py
-
BINaccount_cash_invoice/static/description/icon.png
-
4account_cash_invoice/tests/__init__.py
-
81account_cash_invoice/tests/test_pay_invoice.py
-
5account_cash_invoice/wizard/__init__.py
-
100account_cash_invoice/wizard/cash_invoice_in.py
-
49account_cash_invoice/wizard/cash_invoice_in.xml
-
104account_cash_invoice/wizard/cash_invoice_out.py
-
48account_cash_invoice/wizard/cash_invoice_out.xml
-
56pos_session_pay_invoice/README.rst
-
4pos_session_pay_invoice/__init__.py
-
23pos_session_pay_invoice/__manifest__.py
-
BINpos_session_pay_invoice/static/description/icon.png
-
4pos_session_pay_invoice/tests/__init__.py
-
86pos_session_pay_invoice/tests/test_pay_invoice.py
-
21pos_session_pay_invoice/views/account_bank_statement.xml
-
43pos_session_pay_invoice/views/pos_session.xml
-
5pos_session_pay_invoice/wizard/__init__.py
-
56pos_session_pay_invoice/wizard/cash_invoice_in.py
-
12pos_session_pay_invoice/wizard/cash_invoice_in.xml
-
53pos_session_pay_invoice/wizard/cash_invoice_out.py
-
12pos_session_pay_invoice/wizard/cash_invoice_out.xml
@ -0,0 +1,57 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
=============== |
||||
|
PoS Pay invoice |
||||
|
=============== |
||||
|
|
||||
|
This modules allows to pay an existing Supplier Invoice / Customer Refund, or |
||||
|
to collect payment for an existing Customer Invoice, using the PoS |
||||
|
backend session. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
#. Go to *Point of Sale / Dashboard* and create and/or open an existing |
||||
|
Session. |
||||
|
#. Press the button **Pay Invoice** to pay a Supplier Invoice or a Customer |
||||
|
Refund. You will need to select the expected Journal |
||||
|
#. Select **Collect Payment from Invoice** in to receive a payment of an |
||||
|
existing Customer Invoice or a Supplier Refund. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/repo/github-com-oca-pos-184 |
||||
|
|
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Cannot pay invoices in a different currency than that defined in the journal |
||||
|
associated to the payment method used to pay/collect payment. |
||||
|
|
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Enric Tobella <etobella@creublanca.es> |
||||
|
|
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import models |
||||
|
from . import wizard |
@ -0,0 +1,21 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
{ |
||||
|
'name': 'Account cash invoice', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Account', |
||||
|
'author': "Creu Blanca," |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/pos', |
||||
|
'summary': 'Pay and receive invoices from bank statements', |
||||
|
"license": "LGPL-3", |
||||
|
'depends': [ |
||||
|
"account", |
||||
|
], |
||||
|
'data': [ |
||||
|
"wizard/cash_invoice_out.xml", |
||||
|
"wizard/cash_invoice_in.xml", |
||||
|
], |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import account_bank_statement_line |
@ -0,0 +1,35 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class AccountBankStatementLine(models.Model): |
||||
|
_inherit = 'account.bank.statement.line' |
||||
|
|
||||
|
invoice_id = fields.Many2one( |
||||
|
'account.invoice', |
||||
|
string='Invoice', |
||||
|
readonly=True |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
def fast_counterpart_creation(self): |
||||
|
for st_line in self: |
||||
|
if not st_line.invoice_id: |
||||
|
super( |
||||
|
AccountBankStatementLine, st_line |
||||
|
).fast_counterpart_creation() |
||||
|
else: |
||||
|
invoice = st_line.invoice_id |
||||
|
move_line = invoice.move_id.line_ids.filtered( |
||||
|
lambda r: r.account_id.id == invoice.account_id.id |
||||
|
) |
||||
|
vals = { |
||||
|
'name': st_line.name, |
||||
|
'debit': st_line.amount < 0 and -st_line.amount or 0.0, |
||||
|
'credit': st_line.amount > 0 and st_line.amount or 0.0, |
||||
|
'move_line': move_line |
||||
|
} |
||||
|
st_line.process_reconciliation(counterpart_aml_dicts=[vals]) |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import test_pay_invoice |
@ -0,0 +1,81 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 Creu Blanca <https://creublanca.es/> |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
class TestSessionPayInvoice(common.TransactionCase): |
||||
|
def setUp(self): |
||||
|
super(TestSessionPayInvoice, self).setUp() |
||||
|
self.company = self.env.ref('base.main_company') |
||||
|
partner = self.env.ref('base.partner_demo') |
||||
|
self.invoice_out = self.env['account.invoice'].create({ |
||||
|
'company_id': self.company.id, |
||||
|
'partner_id': partner.id, |
||||
|
'date_invoice': '2016-03-12', |
||||
|
'type': 'out_invoice', |
||||
|
}) |
||||
|
account = self.env['account.account'].create({ |
||||
|
'code': 'test_cash_pay_invoice', |
||||
|
'company_id': self.company.id, |
||||
|
'name': 'Test', |
||||
|
'user_type_id': self.env.ref( |
||||
|
'account.data_account_type_revenue').id |
||||
|
}) |
||||
|
self.env['account.invoice.line'].create({ |
||||
|
'product_id': self.env.ref('product.product_delivery_02').id, |
||||
|
'invoice_id': self.invoice_out.id, |
||||
|
'account_id': account.id, |
||||
|
'name': 'Producto de prueba', |
||||
|
'quantity': 1.0, |
||||
|
'price_unit': 100.0, |
||||
|
}) |
||||
|
self.invoice_out._onchange_invoice_line_ids() |
||||
|
self.invoice_out.action_invoice_open() |
||||
|
self.invoice_out.number = '2999/99999' |
||||
|
self.invoice_in = self.env['account.invoice'].create({ |
||||
|
'partner_id': partner.id, |
||||
|
'company_id': self.company.id, |
||||
|
'type': 'in_invoice', |
||||
|
'date_invoice': '2016-03-12', |
||||
|
}) |
||||
|
self.env['account.invoice.line'].create({ |
||||
|
'product_id': self.env.ref('product.product_delivery_02').id, |
||||
|
'invoice_id': self.invoice_in.id, |
||||
|
'name': 'Producto de prueba', |
||||
|
'account_id': account.id, |
||||
|
'quantity': 1.0, |
||||
|
'price_unit': 100.0, |
||||
|
}) |
||||
|
self.invoice_in._onchange_invoice_line_ids() |
||||
|
self.invoice_in.action_invoice_open() |
||||
|
self.invoice_in.number = '2999/99999' |
||||
|
self.journal = self.env['account.journal'].search([ |
||||
|
('company_id', '=', self.company.id), |
||||
|
('type', '=', 'cash') |
||||
|
], limit=1).ensure_one() |
||||
|
|
||||
|
def test_bank_statement(self): |
||||
|
statement = self.env['account.bank.statement'].create({ |
||||
|
'name': 'Statment', |
||||
|
'journal_id': self.journal.id |
||||
|
}) |
||||
|
in_invoice = self.env['cash.invoice.in'].with_context( |
||||
|
active_ids=statement.ids, active_model='account.bank.statement' |
||||
|
).create({ |
||||
|
'invoice_id': self.invoice_in.id, |
||||
|
'amount': 100.0 |
||||
|
}) |
||||
|
in_invoice.run() |
||||
|
out_invoice = self.env['cash.invoice.out'].with_context( |
||||
|
active_ids=statement.ids, active_model='account.bank.statement' |
||||
|
).create({ |
||||
|
'invoice_id': self.invoice_out.id, |
||||
|
'amount': 100.0 |
||||
|
}) |
||||
|
out_invoice.run() |
||||
|
statement.balance_end_real = statement.balance_start |
||||
|
statement.check_confirm_bank() |
||||
|
self.assertEqual(self.invoice_out.residual, 0.) |
||||
|
self.assertEqual(self.invoice_in.residual, 0.) |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import cash_invoice_in |
||||
|
from . import cash_invoice_out |
@ -0,0 +1,100 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class CashInvoiceIn(models.TransientModel): |
||||
|
_name = 'cash.invoice.in' |
||||
|
_inherit = 'cash.box.out' |
||||
|
|
||||
|
def _default_value(self, default_function): |
||||
|
active_model = self.env.context.get('active_model', False) |
||||
|
if active_model: |
||||
|
active_ids = self.env.context.get('active_ids', False) |
||||
|
return default_function(active_model, active_ids) |
||||
|
return None |
||||
|
|
||||
|
def _default_company(self): |
||||
|
return self._default_value(self.default_company) |
||||
|
|
||||
|
def _default_currency(self): |
||||
|
return self._default_value(self.default_currency) |
||||
|
|
||||
|
def _default_journals(self): |
||||
|
return self._default_value(self.default_journals) |
||||
|
|
||||
|
def _default_journal(self): |
||||
|
journals = self._default_journals() |
||||
|
if journals and len(journals.ids) > 0: |
||||
|
return self.env['account.journal'].browse( |
||||
|
journals.ids[0] |
||||
|
).ensure_one() |
||||
|
|
||||
|
def _default_journal_count(self): |
||||
|
return len(self._default_journals().ids) |
||||
|
|
||||
|
invoice_id = fields.Many2one( |
||||
|
'account.invoice', |
||||
|
string='Invoice', |
||||
|
required=True |
||||
|
) |
||||
|
name = fields.Char( |
||||
|
related='invoice_id.number' |
||||
|
) |
||||
|
company_id = fields.Many2one( |
||||
|
'res.company', |
||||
|
default=_default_company, |
||||
|
required=True, |
||||
|
readonly=True |
||||
|
) |
||||
|
currency_id = fields.Many2one( |
||||
|
'res.currency', |
||||
|
default=_default_currency, |
||||
|
required=True, |
||||
|
readonly=True |
||||
|
) |
||||
|
journal_ids = fields.Many2many( |
||||
|
'account.journal', |
||||
|
default=_default_journals, |
||||
|
required=True, |
||||
|
readonly=True |
||||
|
) |
||||
|
journal_id = fields.Many2one( |
||||
|
'account.journal', |
||||
|
required=True, |
||||
|
default=_default_journal |
||||
|
) |
||||
|
journal_count = fields.Integer( |
||||
|
default=_default_journal_count, |
||||
|
readonly=True |
||||
|
) |
||||
|
|
||||
|
def default_company(self, active_model, active_ids): |
||||
|
return self.env[active_model].browse(active_ids)[0].company_id |
||||
|
|
||||
|
def default_currency(self, active_model, active_ids): |
||||
|
return self.default_company(active_model, active_ids).currency_id |
||||
|
|
||||
|
def default_journals(self, active_model, active_ids): |
||||
|
return self.env[active_model].browse(active_ids)[0].journal_id |
||||
|
|
||||
|
@api.onchange('journal_ids') |
||||
|
def compute_journal_count(self): |
||||
|
self.journal_count = len(self.journal_ids.ids) |
||||
|
|
||||
|
@api.onchange('invoice_id') |
||||
|
def _onchange_invoice(self): |
||||
|
self.amount = self.invoice_id.residual |
||||
|
|
||||
|
@api.multi |
||||
|
def _calculate_values_for_statement_line(self, record): |
||||
|
res = super(CashInvoiceIn, self)._calculate_values_for_statement_line( |
||||
|
record |
||||
|
) |
||||
|
res['invoice_id'] = self.invoice_id.id |
||||
|
res['account_id'] = self.invoice_id.account_id.id |
||||
|
res['ref'] = self.invoice_id.number |
||||
|
res['partner_id'] = self.invoice_id.partner_id.id |
||||
|
return res |
@ -0,0 +1,49 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<record id="cash_invoice_in_form" model="ir.ui.view"> |
||||
|
<field name="name">cash_invoice_in_form</field> |
||||
|
<field name="model">cash.invoice.in</field> |
||||
|
<field name="type">form</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Pay supplier invoice or customer refund"> |
||||
|
<group> |
||||
|
<field name="company_id" invisible="1"/> |
||||
|
<field name="journal_ids" invisible="1"/> |
||||
|
<field name="journal_count" invisible="1"/> |
||||
|
<field name="journal_id" |
||||
|
class="oe_inline" |
||||
|
domain="[('id', 'in', journal_ids[0][2])]" |
||||
|
options="{'no_create': True, 'no_open':True}" |
||||
|
attrs="{'invisible':[('journal_count', '<', 2)]}" |
||||
|
/> |
||||
|
<field name="invoice_id" |
||||
|
class="oe_inline" |
||||
|
domain="[('company_id', '=', company_id), |
||||
|
('state', '=', 'open'), |
||||
|
('currency_id', '=', currency_id), |
||||
|
('type', 'in', ['in_invoice', 'out_refund'])]" |
||||
|
options="{'no_create': True, 'no_open':True}" |
||||
|
/> |
||||
|
<label for="amount"/> |
||||
|
<div> |
||||
|
<field name="amount" class="oe_inline"/><field name="currency_id" class="oe_inline" groups="base.group_multi_currency"/> |
||||
|
</div> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="run" string="Pay invoice" type="object" class="btn-primary"/> |
||||
|
<button special="cancel" string="Cancel" class="btn-default"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<act_window |
||||
|
id="action_cash_invoice_in_from_statement" |
||||
|
name="Pay invoice" |
||||
|
res_model="cash.invoice.in" |
||||
|
src_model="account.bank.statement" |
||||
|
view_mode="form" |
||||
|
target="new" |
||||
|
key2="client_action_multi" |
||||
|
/> |
||||
|
</odoo> |
@ -0,0 +1,104 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class CashInvoiceOut(models.TransientModel): |
||||
|
_name = 'cash.invoice.out' |
||||
|
_inherit = 'cash.box.in' |
||||
|
|
||||
|
def _default_value(self, default_function): |
||||
|
active_model = self.env.context.get('active_model', False) |
||||
|
if active_model: |
||||
|
active_ids = self.env.context.get('active_ids', False) |
||||
|
return default_function(active_model, active_ids) |
||||
|
return None |
||||
|
|
||||
|
def _default_company(self): |
||||
|
return self._default_value(self.default_company) |
||||
|
|
||||
|
def _default_currency(self): |
||||
|
return self._default_value(self.default_currency) |
||||
|
|
||||
|
def _default_journals(self): |
||||
|
return self._default_value(self.default_journals) |
||||
|
|
||||
|
def _default_journal(self): |
||||
|
journals = self._default_journals() |
||||
|
if journals and len(journals.ids) > 0: |
||||
|
return self.env['account.journal'].browse( |
||||
|
journals.ids[0] |
||||
|
).ensure_one() |
||||
|
|
||||
|
def _default_journal_count(self): |
||||
|
return len(self._default_journals()) |
||||
|
|
||||
|
invoice_id = fields.Many2one( |
||||
|
'account.invoice', |
||||
|
string='Invoice', |
||||
|
required=True |
||||
|
) |
||||
|
name = fields.Char( |
||||
|
related='invoice_id.number' |
||||
|
) |
||||
|
company_id = fields.Many2one( |
||||
|
'res.company', |
||||
|
default=_default_company, |
||||
|
required=True, |
||||
|
readonly=True |
||||
|
) |
||||
|
currency_id = fields.Many2one( |
||||
|
'res.currency', |
||||
|
default=_default_currency, |
||||
|
required=True, |
||||
|
readonly=True) |
||||
|
journal_ids = fields.Many2many( |
||||
|
'account.journal', |
||||
|
default=_default_journals, |
||||
|
required=True, |
||||
|
readonly=True |
||||
|
) |
||||
|
journal_id = fields.Many2one( |
||||
|
'account.journal', |
||||
|
required=True, |
||||
|
default=_default_journal |
||||
|
) |
||||
|
journal_count = fields.Integer( |
||||
|
default=_default_journal_count, |
||||
|
readonly=True |
||||
|
) |
||||
|
|
||||
|
def default_company(self, active_model, active_ids): |
||||
|
return self.env[active_model].browse(active_ids)[0].company_id |
||||
|
|
||||
|
def default_currency(self, active_model, active_ids): |
||||
|
return self.default_company(active_model, active_ids).currency_id |
||||
|
|
||||
|
def default_journals(self, active_model, active_ids): |
||||
|
return self.env[active_model].browse(active_ids)[0].journal_id |
||||
|
|
||||
|
@api.onchange('journal_ids') |
||||
|
def compute_journal_count(self): |
||||
|
self.journal_count = len(self.journal_ids.ids) |
||||
|
|
||||
|
@api.onchange('journal_id') |
||||
|
def _onchange_journal(self): |
||||
|
self.currency_id = (self.journal_id.currency_id or |
||||
|
self.journal_id.company_id.currency_id) |
||||
|
|
||||
|
@api.onchange('invoice_id') |
||||
|
def _onchange_invoice(self): |
||||
|
self.amount = self.invoice_id.residual |
||||
|
|
||||
|
@api.multi |
||||
|
def _calculate_values_for_statement_line(self, record): |
||||
|
res = super(CashInvoiceOut, self)._calculate_values_for_statement_line( |
||||
|
record |
||||
|
) |
||||
|
res['invoice_id'] = self.invoice_id.id |
||||
|
res['account_id'] = self.invoice_id.account_id.id |
||||
|
res['ref'] = self.invoice_id.number |
||||
|
res['partner_id'] = self.invoice_id.partner_id.id |
||||
|
return res |
@ -0,0 +1,48 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<record id="cash_invoice_out_form" model="ir.ui.view"> |
||||
|
<field name="name">cash_invoice_out_form</field> |
||||
|
<field name="model">cash.invoice.out</field> |
||||
|
<field name="type">form</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Collect payment from customer invoice or supplier refund"> |
||||
|
<group> |
||||
|
<field name="company_id" invisible="1"/> |
||||
|
<field name="journal_ids" invisible="1"/> |
||||
|
<field name="journal_count" invisible="1"/> |
||||
|
<field name="journal_id" |
||||
|
class="oe_inline" |
||||
|
domain="[('id', 'in', journal_ids[0][2])]" |
||||
|
options="{'no_create': True, 'no_open':True}" |
||||
|
attrs="{'invisible':[('journal_count', '<', 2)]}" |
||||
|
/> |
||||
|
<field name="invoice_id" |
||||
|
class="oe_inline" |
||||
|
domain="[('company_id', '=', company_id), |
||||
|
('state', '=', 'open'), |
||||
|
('currency_id', '=', currency_id), |
||||
|
('type', 'in', ['out_invoice', 'in_refund'])]" |
||||
|
options="{'no_create': True, 'no_open':True}" |
||||
|
/> |
||||
|
<label for="amount"/> |
||||
|
<div> |
||||
|
<field name="amount" class="oe_inline"/><field name="currency_id" class="oe_inline" groups="base.group_multi_currency"/> |
||||
|
</div> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="run" string="Register" type="object" class="btn-primary"/> |
||||
|
<button special="cancel" string="Cancel" class="btn-default"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
<act_window |
||||
|
id="action_cash_invoice_out_from_statement" |
||||
|
name="Collect Payment from Invoice" |
||||
|
res_model="cash.invoice.out" |
||||
|
src_model="account.bank.statement" |
||||
|
view_mode="form" |
||||
|
target="new" |
||||
|
key2="client_action_multi" |
||||
|
/> |
||||
|
</odoo> |
@ -0,0 +1,56 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
======================== |
||||
|
Account cash Pay invoice |
||||
|
======================== |
||||
|
|
||||
|
This modules allows to pay an existing Supplier Invoice / Customer Refund, or |
||||
|
to collect payment for an existing Customer Invoice, using bank statements. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
#. Go to *Accounting / Dashboard* and create and/or open an existing |
||||
|
bank statement. |
||||
|
#. Press the button **Pay Invoice** to pay a Supplier Invoice or a Customer |
||||
|
Refund. You will need to select the expected Journal |
||||
|
#. Select **Collect Payment from Invoice** in to receive a payment of an |
||||
|
existing Customer Invoice or a Supplier Refund. |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/repo/github-com-oca-pos-184 |
||||
|
|
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Cannot pay invoices in a different currency than that defined in the journal |
||||
|
associated to the payment method used to pay/collect payment. |
||||
|
|
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Enric Tobella <etobella@creublanca.es> |
||||
|
|
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import wizard |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
{ |
||||
|
'name': 'POS Pay invoice', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Point Of Sale', |
||||
|
'author': "Creu Blanca," |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/pos', |
||||
|
'summary': 'Pay and receive invoices from PoS Session', |
||||
|
"license": "LGPL-3", |
||||
|
'depends': [ |
||||
|
"point_of_sale", "account_cash_invoice" |
||||
|
], |
||||
|
'data': [ |
||||
|
"wizard/cash_invoice_out.xml", |
||||
|
"wizard/cash_invoice_in.xml", |
||||
|
"views/pos_session.xml", |
||||
|
"views/account_bank_statement.xml", |
||||
|
], |
||||
|
} |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import test_pay_invoice |
@ -0,0 +1,86 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 Creu Blanca <https://creublanca.es/> |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
class TestSessionPayInvoice(common.TransactionCase): |
||||
|
def setUp(self): |
||||
|
super(TestSessionPayInvoice, self).setUp() |
||||
|
self.company = self.env.ref('base.main_company') |
||||
|
partner = self.env.ref('base.partner_demo') |
||||
|
self.invoice_out = self.env['account.invoice'].create({ |
||||
|
'company_id': self.company.id, |
||||
|
'partner_id': partner.id, |
||||
|
'date_invoice': '2016-03-12', |
||||
|
'type': 'out_invoice', |
||||
|
}) |
||||
|
account = self.env['account.account'].create({ |
||||
|
'code': 'test_cash_pay_invoice', |
||||
|
'company_id': self.company.id, |
||||
|
'name': 'Test', |
||||
|
'user_type_id': self.env.ref( |
||||
|
'account.data_account_type_revenue').id |
||||
|
}) |
||||
|
self.env['account.invoice.line'].create({ |
||||
|
'product_id': self.env.ref('product.product_delivery_02').id, |
||||
|
'invoice_id': self.invoice_out.id, |
||||
|
'account_id': account.id, |
||||
|
'name': 'Producto de prueba', |
||||
|
'quantity': 1.0, |
||||
|
'price_unit': 100.0, |
||||
|
}) |
||||
|
self.invoice_out._onchange_invoice_line_ids() |
||||
|
self.invoice_out.action_invoice_open() |
||||
|
self.invoice_out.number = '2999/99999' |
||||
|
self.invoice_in = self.env['account.invoice'].create({ |
||||
|
'partner_id': partner.id, |
||||
|
'company_id': self.company.id, |
||||
|
'type': 'in_invoice', |
||||
|
'date_invoice': '2016-03-12', |
||||
|
}) |
||||
|
self.env['account.invoice.line'].create({ |
||||
|
'product_id': self.env.ref('product.product_delivery_02').id, |
||||
|
'invoice_id': self.invoice_in.id, |
||||
|
'name': 'Producto de prueba', |
||||
|
'account_id': account.id, |
||||
|
'quantity': 1.0, |
||||
|
'price_unit': 100.0, |
||||
|
}) |
||||
|
self.invoice_in._onchange_invoice_line_ids() |
||||
|
self.invoice_in.action_invoice_open() |
||||
|
self.invoice_in.number = '2999/99999' |
||||
|
self.config = self.env.ref('point_of_sale.pos_config_main') |
||||
|
self.config.cash_control = True |
||||
|
|
||||
|
def test_pos_invoice(self): |
||||
|
self.config.open_session_cb() |
||||
|
session = self.config.current_session_id |
||||
|
self.assertIsNotNone(session.statement_ids) |
||||
|
session.action_pos_session_open() |
||||
|
in_invoice = self.env['cash.invoice.in'].with_context( |
||||
|
active_ids=session.ids, active_model='pos.session' |
||||
|
).create({ |
||||
|
'invoice_id': self.invoice_in.id, |
||||
|
'amount': 100.0 |
||||
|
}) |
||||
|
in_invoice.run() |
||||
|
out_invoice = self.env['cash.invoice.out'].with_context( |
||||
|
active_ids=session.ids, active_model='pos.session' |
||||
|
).create({ |
||||
|
'invoice_id': self.invoice_out.id, |
||||
|
'amount': 75.0 |
||||
|
}) |
||||
|
out_invoice.run() |
||||
|
box = self.env['cash.box.in'].with_context( |
||||
|
active_ids=session.ids, active_model='pos.session' |
||||
|
).create({ |
||||
|
'name': "Testing", |
||||
|
'amount': 25.0 |
||||
|
}) |
||||
|
box.run() |
||||
|
session.action_pos_session_closing_control() |
||||
|
session.action_pos_session_closing_control() |
||||
|
self.assertEqual(self.invoice_out.residual, 25.) |
||||
|
self.assertEqual(self.invoice_in.residual, 0.) |
@ -0,0 +1,21 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<act_window |
||||
|
id="action_cash_invoice_in_from_statement" |
||||
|
name="Pay invoice" |
||||
|
res_model="cash.invoice.in" |
||||
|
src_model="account.bank.statement" |
||||
|
view_mode="form" |
||||
|
target="new" |
||||
|
key2="client_action_multi" |
||||
|
/> |
||||
|
<act_window |
||||
|
id="action_cash_invoice_out_from_statement" |
||||
|
name="Collect Payment from Invoice" |
||||
|
res_model="cash.invoice.out" |
||||
|
src_model="account.bank.statement" |
||||
|
view_mode="form" |
||||
|
target="new" |
||||
|
key2="client_action_multi" |
||||
|
/> |
||||
|
</odoo> |
@ -0,0 +1,43 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<record id="view_pos_config_kanban" model="ir.ui.view"> |
||||
|
<field name="name">pos.config.kanban.view</field> |
||||
|
<field name="model">pos.config</field> |
||||
|
<field name="inherit_id" ref="point_of_sale.view_pos_config_kanban"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<button name="open_ui" position="after"> |
||||
|
<button t-if="record.current_session_state.raw_value === 'opened'" |
||||
|
class="btn btn-info" |
||||
|
name="open_existing_session_cb" |
||||
|
type="object">Summary</button> |
||||
|
</button> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_pos_session_form" model="ir.ui.view"> |
||||
|
<field name="name">pos.session.form.view</field> |
||||
|
<field name="model">pos.session</field> |
||||
|
<field name="inherit_id" ref="point_of_sale.view_pos_session_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<button name="open_cashbox" position="after"> |
||||
|
<button class="oe_stat_button" name="%(action_pos_invoice_in)d" |
||||
|
type="action" icon="fa-level-up" |
||||
|
attrs="{'invisible':['|','|',('cash_control', '=', False),('state', 'not in', ['opened', 'closing_control']), ('statement_ids', '=', False)]}"> |
||||
|
<div class="o_form_field o_stat_info"> |
||||
|
<span class="o_stat_text">Pay</span> |
||||
|
<span class="o_stat_text">Invoice</span> |
||||
|
</div> |
||||
|
</button> |
||||
|
<button class="oe_stat_button" name="%(action_pos_invoice_out)d" |
||||
|
type="action" icon="fa-level-down" |
||||
|
attrs="{'invisible':['|',('state', 'not in', ['opened', 'closing_control']), ('statement_ids', '=', False)]}"> |
||||
|
<div class="o_form_field o_stat_info"> |
||||
|
<span class="o_stat_text">Collect Payment</span> |
||||
|
<span class="o_stat_text">from Invoice</span> |
||||
|
</div> |
||||
|
</button> |
||||
|
|
||||
|
</button> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import cash_invoice_in |
||||
|
from . import cash_invoice_out |
@ -0,0 +1,56 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import api, models, _ |
||||
|
from odoo.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
class CashInvoiceIn(models.TransientModel): |
||||
|
_inherit = 'cash.invoice.in' |
||||
|
|
||||
|
def default_company(self, active_model, active_ids): |
||||
|
if active_model == 'pos.session': |
||||
|
active = self.env[active_model].browse(active_ids) |
||||
|
return active[0].config_id.company_id |
||||
|
return super(CashInvoiceIn, self).default_company( |
||||
|
active_model, active_ids |
||||
|
) |
||||
|
|
||||
|
def default_journals(self, active_model, active_ids): |
||||
|
if active_model == 'pos.session': |
||||
|
active = self.env[active_model].browse(active_ids) |
||||
|
if not active.cash_register_id: |
||||
|
raise UserError(_( |
||||
|
"There is no cash register for this Pos session" |
||||
|
)) |
||||
|
return active.cash_register_id.journal_id |
||||
|
return super(CashInvoiceIn, self).default_journals( |
||||
|
active_model, active_ids |
||||
|
) |
||||
|
|
||||
|
def default_currency(self, active_model, active_ids): |
||||
|
if active_model == 'pos.session': |
||||
|
journal = self._default_journal() |
||||
|
if journal.currency_id: |
||||
|
return journal.currency_id |
||||
|
return super(CashInvoiceIn, self).default_currency( |
||||
|
active_model, active_ids |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
def run(self): |
||||
|
active_model = self.env.context.get('active_model', False) |
||||
|
active_ids = self.env.context.get('active_ids', False) |
||||
|
if active_model == 'pos.session': |
||||
|
bank_statements = [ |
||||
|
session.statement_ids.filtered( |
||||
|
lambda r: r.journal_id.id == self.journal_id.id |
||||
|
) |
||||
|
for session in self.env[active_model].browse(active_ids) |
||||
|
] |
||||
|
if not bank_statements: |
||||
|
raise UserError(_('Bank Statement was not found')) |
||||
|
return self._run(bank_statements) |
||||
|
else: |
||||
|
return super(CashInvoiceIn, self).run() |
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<act_window |
||||
|
id="action_pos_invoice_in" |
||||
|
name="Pay invoice" |
||||
|
res_model="cash.invoice.in" |
||||
|
src_model="pos.session" |
||||
|
view_mode="form" |
||||
|
target="new" |
||||
|
key2="client_action_multi" |
||||
|
/> |
||||
|
</odoo> |
@ -0,0 +1,53 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright (C) 2017 Creu Blanca |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from odoo import api, models, _ |
||||
|
from odoo.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
class CashInvoiceOut(models.TransientModel): |
||||
|
_inherit = 'cash.invoice.out' |
||||
|
|
||||
|
def default_company(self, active_model, active_ids): |
||||
|
if active_model == 'pos.session': |
||||
|
active = self.env[active_model].browse(active_ids) |
||||
|
return active[0].config_id.company_id |
||||
|
return super(CashInvoiceOut, self).default_company( |
||||
|
active_model, active_ids |
||||
|
) |
||||
|
|
||||
|
def default_currency(self, active_model, active_ids): |
||||
|
if active_model == 'pos.session': |
||||
|
journal = self._default_journal() |
||||
|
if journal.currency_id: |
||||
|
return journal.currency_id |
||||
|
return super(CashInvoiceOut, self).default_currency( |
||||
|
active_model, active_ids |
||||
|
) |
||||
|
|
||||
|
def default_journals(self, active_model, active_ids): |
||||
|
if active_model == 'pos.session': |
||||
|
active = self.env[active_model].browse(active_ids) |
||||
|
return self.env['account.journal'].browse( |
||||
|
[r.journal_id.id for r in active.statement_ids]) |
||||
|
return super(CashInvoiceOut, self).default_journals( |
||||
|
active_model, active_ids |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
def run(self): |
||||
|
active_model = self.env.context.get('active_model', False) |
||||
|
active_ids = self.env.context.get('active_ids', False) |
||||
|
if active_model == 'pos.session': |
||||
|
bank_statements = [ |
||||
|
session.statement_ids.filtered( |
||||
|
lambda r: r.journal_id.id == self.journal_id.id |
||||
|
) |
||||
|
for session in self.env[active_model].browse(active_ids) |
||||
|
] |
||||
|
if not bank_statements: |
||||
|
raise UserError(_('Bank Statement was not found')) |
||||
|
return self._run(bank_statements) |
||||
|
else: |
||||
|
return super(CashInvoiceOut, self).run() |
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<act_window |
||||
|
id="action_pos_invoice_out" |
||||
|
name="Pay invoice" |
||||
|
res_model="cash.invoice.out" |
||||
|
src_model="pos.session" |
||||
|
view_mode="form" |
||||
|
target="new" |
||||
|
key2="client_action_multi" |
||||
|
/> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue