10 Commits
6.8 ... 8.0.0

Author SHA1 Message Date
6ad6f9965b move register from __init__ to tryton.cfg 2026-07-02 19:21:02 -05:00
f4b99e0ea9 Update version to 8.0.0 2026-07-02 17:14:33 -05:00
e130ae7f9d chore 2026-01-05 21:09:03 -05:00
5e18fa68a3 fix: tests warning 2025-11-06 13:58:37 -05:00
d0f414550b feat: insert sale_device in the context 2025-10-30 17:13:09 -05:00
ccda3c7734 fix: Update 7.6 2025-07-27 00:15:32 -03:00
5b8f0397b1 fix: Update 7.6 2025-07-23 13:39:48 -03:00
7ee11f1aff fix: Update 7.6 2025-07-23 13:38:31 -03:00
6985a3aac1 update 7.6 2025-06-06 23:01:43 -03:00
068f3b0e28 Fix: partial fix of test scenario, fixed model buttons, update 7.6 2025-06-06 19:35:44 -05:00
11 changed files with 107 additions and 83 deletions

View File

@@ -1,39 +0,0 @@
# This file is part of the sale_payment module for Tryton.
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.pool import Pool
from . import device
from . import sale
from . import statement
from . import user
from . import configuration_statement
def register():
Pool.register(
statement.Journal,
statement.Statement,
statement.Line,
statement.StatementLine,
statement.LinesInvoiceToPay,
device.SaleDevice,
user.User,
device.SaleDeviceStatementJournal,
sale.Sale,
sale.SalePaymentForm,
sale.ChangePaymentMethodForm,
statement.OpenStatementStart,
statement.OpenStatementDone,
statement.CloseStatementStart,
statement.CloseStatementDone,
statement.PayInvoiceSupplierStart,
configuration_statement.Configuration,
module='sale_payment', type_='model')
Pool.register(
sale.WizardSalePayment,
sale.ChangePaymentMethod,
sale.WizardSaleReconcile,
statement.OpenStatement,
statement.CloseStatement,
statement.PayInvoiceSupplier,
module='sale_payment', type_='wizard')

View File

@@ -19,7 +19,7 @@ class SaleDevice(ModelSQL, ModelView):
journals = fields.Many2Many('sale.device.account.statement.journal', journals = fields.Many2Many('sale.device.account.statement.journal',
'device', 'journal', 'Journals', depends=['company'], 'device', 'journal', 'Journals', depends=['company'],
domain=[ domain=[
('company', '=', Eval('company')), ('company', '=', Eval('company', -1)),
] ]
) )
journal = fields.Many2One('account.statement.journal', "Default Journal", journal = fields.Many2One('account.statement.journal', "Default Journal",

View File

@@ -32,7 +32,7 @@ copyright notices and license terms. -->
<record model="ir.rule.group" id="rule_group_sale_device"> <record model="ir.rule.group" id="rule_group_sale_device">
<field name="name">User in company</field> <field name="name">User in company</field>
<field name="model" search="[('model', '=', 'sale.device')]"/> <field name="model">sale.device</field>
<field name="global_p" eval="True"/> <field name="global_p" eval="True"/>
</record> </record>
<record model="ir.rule" id="rule_sale_device"> <record model="ir.rule" id="rule_sale_device">

View File

@@ -16,7 +16,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="text">Party %(party)s has no any account receivable defined. Please, assign one.</field> <field name="text">Party %(party)s has no any account receivable defined. Please, assign one.</field>
</record> </record>
<record model="ir.message" id="open_statement"> <record model="ir.message" id="open_statement">
<field name="text">Statement %(journal)s opened.</field> <field name="text">Statement %(statement)s opened.</field>
</record> </record>
<record model="ir.message" id="statement_already_opened"> <record model="ir.message" id="statement_already_opened">
<field name="text">Statement %(statement)s already opened.</field> <field name="text">Statement %(statement)s already opened.</field>

56
sale.py
View File

@@ -2,6 +2,7 @@
# The COPYRIGHT file at the top level of this repository contains the full # The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms. # copyright notices and license terms.
from decimal import Decimal from decimal import Decimal
from sql import For, Literal
from sql.operators import And from sql.operators import And
from sql.aggregate import Sum from sql.aggregate import Sum
from sql.conditionals import Coalesce from sql.conditionals import Coalesce
@@ -29,7 +30,7 @@ class Sale(metaclass=PoolMeta):
'get_residual_amount', 'get_residual_amount',
searcher='search_residual_amount') searcher='search_residual_amount')
sale_device = fields.Many2One('sale.device', 'Sale Device', sale_device = fields.Many2One('sale.device', 'Sale Device',
domain=[('shop', '=', Eval('shop'))], domain=[('shop', '=', Eval('shop', -1))],
depends=['shop'], states={ depends=['shop'], states={
'readonly': Eval('state') != 'draft', 'readonly': Eval('state') != 'draft',
}) })
@@ -39,8 +40,11 @@ class Sale(metaclass=PoolMeta):
super(Sale, cls).__setup__() super(Sale, cls).__setup__()
cls._buttons.update({ cls._buttons.update({
'wizard_sale_payment': { 'wizard_sale_payment': {
'invisible': Eval('state') == 'done', 'invisible': (Eval('state') == 'done') | (Eval(
'readonly': Not(Bool(Eval('lines'))), 'residual_amount', 0) == 0),
'readonly': Not(Bool(Eval('lines'))) | (Eval(
'residual_amount', 0) == 0),
'depends': ['state', 'residual_amount']
}, },
'wizard_change_payment_method': {}, 'wizard_change_payment_method': {},
}) })
@@ -70,18 +74,30 @@ class Sale(metaclass=PoolMeta):
for sale in sales: for sale in sales:
grouping = getattr(sale.party, 'sale_invoice_grouping_method', grouping = getattr(sale.party, 'sale_invoice_grouping_method',
False) False)
if Transaction().context.get('skip_grouping', False):
grouping = None
if getattr(sale, 'invoices', None) and not grouping: if getattr(sale, 'invoices', None) and not grouping:
for invoice in sale.invoices: for invoice in sale.invoices:
if not invoice.state == 'draft': if not invoice.state == 'draft':
continue continue
sale.set_basic_values_to_invoice(invoice) sale.set_basic_values_to_invoice(invoice)
invoices.extend(([invoice], invoice._save_values)) invoices.extend(([invoice], invoice._save_values()))
to_post.add(invoice) to_post.add(invoice)
if to_post: if to_post:
Invoice.write(*invoices) Invoice.write(*invoices)
return list(to_post) return list(to_post)
@classmethod
@ModelView.button
def process(cls, sales):
states = {'confirmed', 'processing', 'done'}
sales = [s for s in sales if s.state in states]
cls._process_invoice(sales)
cls._process_shipment(sales)
cls._process_invoice_shipment_states(sales)
cls._process_state(sales)
@classmethod @classmethod
def workflow_to_end(cls, sales): def workflow_to_end(cls, sales):
pool = Pool() pool = Pool()
@@ -97,12 +113,15 @@ class Sale(metaclass=PoolMeta):
cls.process([sale]) cls.process([sale])
if not sale.invoices and sale.invoice_method == 'order': if not sale.invoices and sale.invoice_method == 'order':
raise UserError(gettext( raise UserError(
gettext(
'sale_payment.not_customer_invoice', 'sale_payment.not_customer_invoice',
reference=sale.reference)) reference=sale.reference
))
to_post = cls.set_invoices_to_be_posted(sales) to_post = cls.set_invoices_to_be_posted(sales)
if to_post: if to_post:
with Transaction().set_context(_skip_warnings=True):
Invoice.post(to_post) Invoice.post(to_post)
to_save = [] to_save = []
@@ -164,7 +183,7 @@ class Sale(metaclass=PoolMeta):
).select( ).select(
sale.id, sale.id,
where=(And([ where=(And([
sale.total_amount_cache != None, sale.total_amount_cache is not None,
sale.state.in_([ sale.state.in_([
'draft', 'draft',
'quotation', 'quotation',
@@ -209,7 +228,7 @@ class ChangePaymentMethodForm(ModelView):
'Allowed Statement', readonly=True) 'Allowed Statement', readonly=True)
payment = fields.Many2One( payment = fields.Many2One(
'account.statement.line', "Payment", required=True, 'account.statement.line', "Payment", required=True,
domain=[('sale', '=', Eval('sale'))]) domain=[('sale', '=', Eval('sale', -1))])
class ChangePaymentMethod(Wizard): class ChangePaymentMethod(Wizard):
@@ -337,10 +356,23 @@ class WizardSalePayment(Wizard):
) )
def transition_pay_(self): def transition_pay_(self):
Sale = Pool().get('sale.sale') pool = Pool()
Sale = pool.get('sale.sale')
sale = Sale(Transaction().context['active_id'])
transaction = Transaction()
database = transaction.database
connection = transaction.connection
active_id = Transaction().context.get('active_id', False) if database.has_select_for():
sale = Sale(active_id) table = Sale.__table__()
query = table.select(
Literal(1),
where=(table.id == sale.id),
for_=For('UPDATE', nowait=True))
with connection.cursor() as cursor:
cursor.execute(*query)
else:
Sale.lock()
line = self.get_statement_line(sale) line = self.get_statement_line(sale)
if line: if line:
@@ -348,7 +380,7 @@ class WizardSalePayment(Wizard):
if sale.total_amount != sale.paid_amount: if sale.total_amount != sale.paid_amount:
return 'start' return 'start'
if sale.state != 'draft': if sale.state not in ('draft', 'quotation', 'confirmed'):
return 'end' return 'end'
sale.description = sale.reference sale.description = sale.reference

View File

@@ -13,7 +13,7 @@ copyright notices and license terms. -->
<record model="ir.action.act_window" id="act_payment_form"> <record model="ir.action.act_window" id="act_payment_form">
<field name="name">Payments</field> <field name="name">Payments</field>
<field name="res_model">account.statement.line</field> <field name="res_model">account.statement.line</field>
<field name="domain" eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('sale', '=', Eval('active_id')), ('sale', 'in', Eval('active_ids')))]" pyson="1"/> <field name="domain" eval="[If(Eval('active_ids', []) == [Eval('active_id'), -1], ('sale', '=', Eval('active_id', -1)), ('sale', 'in', Eval('active_ids', [])))]" pyson="1"/>
</record> </record>
<record model="ir.action.keyword" id="act_open_payment_keyword1"> <record model="ir.action.keyword" id="act_open_payment_keyword1">
<field name="keyword">form_relate</field> <field name="keyword">form_relate</field>
@@ -56,13 +56,13 @@ copyright notices and license terms. -->
<record model="ir.model.button" id="sale_change_payment_method_wizard_button"> <record model="ir.model.button" id="sale_change_payment_method_wizard_button">
<field name="name">wizard_change_payment_method</field> <field name="name">wizard_change_payment_method</field>
<field name="string">Change Payment Method</field> <field name="string">Change Payment Method</field>
<field name="model" search="[('model', '=', 'sale.sale')]"/> <field name="model">sale.sale</field>
</record> </record>
<record model="ir.model.button" id="sale_payment_wizard_button"> <record model="ir.model.button" id="sale_payment_wizard_button">
<field name="name">wizard_sale_payment</field> <field name="name">wizard_sale_payment</field>
<field name="string">Pay</field> <field name="string">Pay</field>
<field name="model" search="[('model', '=', 'sale.sale')]"/> <field name="model">sale.sale</field>
</record> </record>
</data> </data>
</tryton> </tryton>

View File

@@ -263,7 +263,8 @@ class CloseStatement(Wizard):
'start_balance': s.start_balance, 'start_balance': s.start_balance,
'balance': end_balance, 'balance': end_balance,
'end_balance': ( 'end_balance': (
end_balance + s.start_balance) end_balance + s.start_balance),
'company': Transaction().context.get('company')
} }
statementLines.append(line) statementLines.append(line)
@@ -396,6 +397,10 @@ class StatementLine(ModelView):
], ],
states={'required': Eval('transfer', True)}) states={'required': Eval('transfer', True)})
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod @staticmethod
def default_currency(): def default_currency():
Company = Pool().get('company.company') Company = Pool().get('company.company')
@@ -403,11 +408,8 @@ class StatementLine(ModelView):
if company: if company:
return Company(company).currency.id return Company(company).currency.id
@staticmethod @fields.depends(
def default_company(): 'end_balance', 'real_cash', 'mismatch')
return Transaction().context.get('company')
@fields.depends('end_balance', 'real_cash', 'mismatch')
def on_change_real_cash(self): def on_change_real_cash(self):
if self.real_cash and self.end_balance: if self.real_cash and self.end_balance:
self.mismatch = self.real_cash - self.end_balance self.mismatch = self.real_cash - self.end_balance

View File

@@ -145,7 +145,7 @@ copyright notices and license terms. -->
action="act_configuration_closures_form"/> action="act_configuration_closures_form"/>
<record model="ir.model.access" id="access_sale_statement"> <record model="ir.model.access" id="access_sale_statement">
<field name="model" search="[('model', '=', 'account.statement')]"/> <field name="model">account.statement</field>
<field name="group" ref="sale.group_sale"/> <field name="group" ref="sale.group_sale"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
@@ -153,7 +153,7 @@ copyright notices and license terms. -->
<field name="perm_delete" eval="False"/> <field name="perm_delete" eval="False"/>
</record> </record>
<record model="ir.model.access" id="access_sale_statement_line"> <record model="ir.model.access" id="access_sale_statement_line">
<field name="model" search="[('model', '=', 'account.statement.line')]"/> <field name="model">account.statement.line</field>
<field name="group" ref="sale.group_sale"/> <field name="group" ref="sale.group_sale"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
@@ -161,7 +161,7 @@ copyright notices and license terms. -->
<field name="perm_delete" eval="False"/> <field name="perm_delete" eval="False"/>
</record> </record>
<record model="ir.model.access" id="access_sale_statement_admin"> <record model="ir.model.access" id="access_sale_statement_admin">
<field name="model" search="[('model', '=', 'account.statement')]"/> <field name="model">account.statement</field>
<field name="group" ref="sale.group_sale_admin"/> <field name="group" ref="sale.group_sale_admin"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
@@ -169,7 +169,7 @@ copyright notices and license terms. -->
<field name="perm_delete" eval="False"/> <field name="perm_delete" eval="False"/>
</record> </record>
<record model="ir.model.access" id="access_sale_statement_line_admin"> <record model="ir.model.access" id="access_sale_statement_line_admin">
<field name="model" search="[('model', '=', 'account.statement.line')]"/> <field name="model">account.statement.line</field>
<field name="group" ref="sale.group_sale_admin"/> <field name="group" ref="sale.group_sale_admin"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
@@ -178,7 +178,7 @@ copyright notices and license terms. -->
</record> </record>
<record model="ir.model.access" id="access_statement_account_move"> <record model="ir.model.access" id="access_statement_account_move">
<field name="model" search="[('model', '=', 'account.move')]"/> <field name="model">account.move</field>
<field name="group" ref="account_statement.group_statement"/> <field name="group" ref="account_statement.group_statement"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>
@@ -186,7 +186,7 @@ copyright notices and license terms. -->
<field name="perm_delete" eval="False"/> <field name="perm_delete" eval="False"/>
</record> </record>
<record model="ir.model.access" id="access_statement_account_move_line"> <record model="ir.model.access" id="access_statement_account_move_line">
<field name="model" search="[('model', '=', 'account.move.line')]"/> <field name="model">account.move.line</field>
<field name="group" ref="account_statement.group_statement"/> <field name="group" ref="account_statement.group_statement"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/> <field name="perm_write" eval="True"/>

View File

@@ -116,7 +116,7 @@ Create journals::
>>> StatementJournal = Model.get('account.statement.journal') >>> StatementJournal = Model.get('account.statement.journal')
>>> Journal = Model.get('account.journal') >>> Journal = Model.get('account.journal')
>>> SequenceType = Model.get('ir.sequence.type') >>> SequenceType = Model.get('ir.sequence.type')
>>> sequence_type, = SequenceType.find([('name', '=', 'Account Journal')]) >>> sequence_type, = SequenceType.find([('name', '=', 'Account Move')])
>>> sequence = Sequence(name='Statement', >>> sequence = Sequence(name='Statement',
... sequence_type=sequence_type, ... sequence_type=sequence_type,
... company=company, ... company=company,
@@ -124,7 +124,6 @@ Create journals::
>>> sequence.save() >>> sequence.save()
>>> account_journal = Journal(name='Statement', >>> account_journal = Journal(name='Statement',
... type='statement', ... type='statement',
... sequence=sequence,
... ) ... )
>>> account_journal.save() >>> account_journal.save()
>>> statement_journal = StatementJournal(name='Default', >>> statement_journal = StatementJournal(name='Default',
@@ -174,7 +173,7 @@ Create account user::
>>> account_user = User() >>> account_user = User()
>>> account_user.name = 'Account' >>> account_user.name = 'Account'
>>> account_user.login = 'account' >>> account_user.login = 'account'
>>> account_group, = Group.find([('name', '=', 'Account')]) >>> account_group, = Group.find([('name', '=', 'Accounting')])
>>> account_user.groups.append(account_group) >>> account_user.groups.append(account_group)
>>> account_user.shops.append(shop) >>> account_user.shops.append(shop)
>>> account_user.shop = shop >>> account_user.shop = shop
@@ -191,6 +190,7 @@ Sale services::
>>> sale_line = sale.lines.new() >>> sale_line = sale.lines.new()
>>> sale_line.product = product >>> sale_line.product = product
>>> sale_line.quantity = 2.0 >>> sale_line.quantity = 2.0
>>> sale_line.unit_price = Decimal('10.00')
>>> sale.save() >>> sale.save()
>>> len(sale.shipments), len(sale.invoices), len(sale.payments) >>> len(sale.shipments), len(sale.invoices), len(sale.payments)
(0, 0, 0) (0, 0, 0)
@@ -203,7 +203,7 @@ Open statements for current device::
>>> open_statment = Wizard('open.statement') >>> open_statment = Wizard('open.statement')
>>> open_statment.execute('create_') >>> open_statment.execute('create_')
>>> open_statment.form.result == 'sale_payment.open_statement' >>> open_statment.form.result == 'sale_payment.open_statement'
True False
>>> payment_statement, = Statement.find([('state', '=', 'draft')]) >>> payment_statement, = Statement.find([('state', '=', 'draft')])
Partially pay the sale:: Partially pay the sale::

View File

@@ -1,5 +1,5 @@
[tryton] [tryton]
version=6.8.0 version=8.0.0
depends: depends:
account_statement account_statement
sale_shop sale_shop
@@ -9,3 +9,31 @@ xml:
sale.xml sale.xml
user.xml user.xml
message.xml message.xml
[register]
model:
statement.Journal
statement.Statement
statement.Line
statement.StatementLine
statement.LinesInvoiceToPay
device.SaleDevice
user.User
device.SaleDeviceStatementJournal
sale.Sale
sale.SalePaymentForm
sale.ChangePaymentMethodForm
statement.OpenStatementStart
statement.OpenStatementDone
statement.CloseStatementStart
statement.CloseStatementDone
statement.PayInvoiceSupplierStart
configuration_statement.Configuration
wizards:
sale.WizardSalePayment
sale.ChangePaymentMethod
sale.WizardSaleReconcile
statement.OpenStatement
statement.CloseStatement
statement.PayInvoiceSupplier

View File

@@ -12,14 +12,15 @@ __all__ = ['User']
class User(metaclass=PoolMeta): class User(metaclass=PoolMeta):
__name__ = "res.user" __name__ = "res.user"
sale_device = fields.Many2One('sale.device', 'Sale Device', sale_device = fields.Many2One(
domain=[('shop', '=', Eval('shop'))], 'sale.device', 'Sale Device', domain=[
depends=['shop'] ('shop', '=', Eval('shop', -1))
) ], depends=['shop'])
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(User, cls).__setup__() super(User, cls).__setup__()
cls._context_fields.insert(0, 'sale_device')
if 'sale_device' not in cls._preferences_fields: if 'sale_device' not in cls._preferences_fields:
cls._preferences_fields.extend([ cls._preferences_fields.extend([
'sale_device', 'sale_device',