first commit
This commit is contained in:
2
modules/sale_credit_limit/__init__.py
Normal file
2
modules/sale_credit_limit/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
BIN
modules/sale_credit_limit/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
modules/sale_credit_limit/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
modules/sale_credit_limit/__pycache__/party.cpython-311.pyc
Normal file
BIN
modules/sale_credit_limit/__pycache__/party.cpython-311.pyc
Normal file
Binary file not shown.
BIN
modules/sale_credit_limit/__pycache__/sale.cpython-311.pyc
Normal file
BIN
modules/sale_credit_limit/__pycache__/sale.cpython-311.pyc
Normal file
Binary file not shown.
67
modules/sale_credit_limit/party.py
Normal file
67
modules/sale_credit_limit/party.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from decimal import Decimal
|
||||
from itertools import groupby
|
||||
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.tools import grouped_slice
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class Party(metaclass=PoolMeta):
|
||||
__name__ = 'party.party'
|
||||
|
||||
@classmethod
|
||||
def get_credit_amount(cls, parties, name):
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
Sale = pool.get('sale.sale')
|
||||
Uom = pool.get('product.uom')
|
||||
|
||||
amounts = super().get_credit_amount(parties, name)
|
||||
|
||||
company_id = Transaction().context.get('company')
|
||||
|
||||
for sub_parties in grouped_slice(parties):
|
||||
id2party = {p.id: p for p in sub_parties}
|
||||
|
||||
sales = Sale.search([
|
||||
('company', '=', company_id),
|
||||
('party', 'in', list(id2party.keys())),
|
||||
('state', 'in', ['confirmed', 'processing']),
|
||||
],
|
||||
order=[('party', None)])
|
||||
for party_id, sales in groupby(sales, lambda s: s.party.id):
|
||||
party = id2party[party_id]
|
||||
for sale in sales:
|
||||
amount = 0
|
||||
for line in sale.lines:
|
||||
quantity = line.credit_limit_quantity
|
||||
if quantity is None:
|
||||
continue
|
||||
for invoice_line in line.invoice_lines:
|
||||
if invoice_line.type != 'line':
|
||||
continue
|
||||
invoice = invoice_line.invoice
|
||||
if invoice and invoice.move:
|
||||
if line.unit:
|
||||
quantity -= Uom.compute_qty(
|
||||
invoice_line.unit or line.unit,
|
||||
invoice_line.quantity,
|
||||
line.unit, round=False)
|
||||
else:
|
||||
quantity -= invoice_line.quantity
|
||||
if quantity > 0:
|
||||
amount += Currency.compute(
|
||||
sale.currency,
|
||||
Decimal(str(quantity)) * line.unit_price,
|
||||
party.currency,
|
||||
round=False)
|
||||
amounts[party.id] = party.currency.round(
|
||||
amounts[sale.party.id] + amount)
|
||||
return amounts
|
||||
|
||||
@classmethod
|
||||
def _credit_limit_to_lock(cls):
|
||||
return super()._credit_limit_to_lock() + [
|
||||
'sale.sale', 'sale.line']
|
||||
52
modules/sale_credit_limit/sale.py
Normal file
52
modules/sale_credit_limit/sale.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import ModelView, Workflow
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
|
||||
|
||||
class Sale(metaclass=PoolMeta):
|
||||
__name__ = 'sale.sale'
|
||||
|
||||
def must_check_credit_limit(self):
|
||||
return self.shipment_method == 'order'
|
||||
|
||||
@property
|
||||
def credit_limit_amount(self):
|
||||
"Amount to check against credit limit"
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
return Currency.compute(
|
||||
self.currency, self.untaxed_amount, self.company.currency)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('confirmed')
|
||||
def confirm(cls, sales):
|
||||
for sale in sales:
|
||||
if sale.must_check_credit_limit():
|
||||
party = sale.invoice_party or sale.party
|
||||
party.check_credit_limit(
|
||||
sale.credit_limit_amount, sale.company, origin=sale)
|
||||
super().confirm(sales)
|
||||
|
||||
|
||||
class Line(metaclass=PoolMeta):
|
||||
__name__ = 'sale.line'
|
||||
|
||||
@property
|
||||
def credit_limit_quantity(self):
|
||||
pool = Pool()
|
||||
UoM = pool.get('product.uom')
|
||||
if (self.type != 'line') or (self.quantity <= 0):
|
||||
return None
|
||||
quantity = self.quantity
|
||||
if self.sale.invoice_method == 'shipment':
|
||||
for move in self.moves_ignored:
|
||||
quantity -= UoM.compute_qty(
|
||||
move.unit, move.quantity, self.unit, round=False)
|
||||
for invoice_line in self.invoice_lines:
|
||||
if invoice_line.invoice in self.sale.invoices_ignored:
|
||||
quantity -= UoM.compute_qty(
|
||||
invoice_line.unit, invoice_line.quantity, self.unit,
|
||||
round=False)
|
||||
return quantity
|
||||
2
modules/sale_credit_limit/tests/__init__.py
Normal file
2
modules/sale_credit_limit/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
Binary file not shown.
Binary file not shown.
131
modules/sale_credit_limit/tests/test_module.py
Normal file
131
modules/sale_credit_limit/tests/test_module.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from trytond.exceptions import UserWarning
|
||||
from trytond.modules.account.tests import create_chart, get_fiscalyear
|
||||
from trytond.modules.account_invoice.tests import set_invoice_sequences
|
||||
from trytond.modules.company.tests import (
|
||||
CompanyTestMixin, create_company, set_company)
|
||||
from trytond.pool import Pool
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
|
||||
|
||||
class SaleCreditLimitTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
'Test SaleCreditLimit module'
|
||||
module = 'sale_credit_limit'
|
||||
|
||||
@with_transaction()
|
||||
def test_check_credit_limit(self):
|
||||
'Test check_credit_limit'
|
||||
pool = Pool()
|
||||
Account = pool.get('account.account')
|
||||
Move = pool.get('account.move')
|
||||
Journal = pool.get('account.journal')
|
||||
Party = pool.get('party.party')
|
||||
Sale = pool.get('sale.sale')
|
||||
PaymentTerm = pool.get('account.invoice.payment_term')
|
||||
Configuration = pool.get('account.configuration')
|
||||
FiscalYear = pool.get('account.fiscalyear')
|
||||
Invoice = pool.get('account.invoice')
|
||||
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
create_chart(company)
|
||||
fiscalyear = set_invoice_sequences(get_fiscalyear(company))
|
||||
fiscalyear.save()
|
||||
FiscalYear.create_period([fiscalyear])
|
||||
period = fiscalyear.periods[0]
|
||||
|
||||
receivable, = Account.search([
|
||||
('closed', '!=', True),
|
||||
('type.receivable', '=', True),
|
||||
('party_required', '=', True),
|
||||
('company', '=', company.id),
|
||||
], limit=1)
|
||||
revenue, = Account.search([
|
||||
('closed', '!=', True),
|
||||
('type.revenue', '=', True),
|
||||
('company', '=', company.id),
|
||||
], limit=1)
|
||||
journal, = Journal.search([], limit=1)
|
||||
party, = Party.create([{
|
||||
'name': 'Party',
|
||||
'addresses': [
|
||||
('create', [{}]),
|
||||
],
|
||||
'credit_limit_amount': Decimal('100'),
|
||||
}])
|
||||
Move.create([{
|
||||
'journal': journal.id,
|
||||
'period': period.id,
|
||||
'date': period.start_date,
|
||||
'lines': [
|
||||
('create', [{
|
||||
'debit': Decimal('100'),
|
||||
'account': receivable.id,
|
||||
'party': party.id,
|
||||
}, {
|
||||
'credit': Decimal('100'),
|
||||
'account': revenue.id,
|
||||
}]),
|
||||
],
|
||||
}])
|
||||
payment_term, = PaymentTerm.create([{
|
||||
'name': 'Test',
|
||||
'lines': [
|
||||
('create', [{
|
||||
'type': 'remainder',
|
||||
}])
|
||||
],
|
||||
}])
|
||||
config = Configuration(1)
|
||||
config.default_category_account_revenue = revenue
|
||||
config.save()
|
||||
sale, = Sale.create([{
|
||||
'party': party.id,
|
||||
'company': company.id,
|
||||
'payment_term': payment_term.id,
|
||||
'currency': company.currency.id,
|
||||
'invoice_address': party.addresses[0].id,
|
||||
'shipment_address': party.addresses[0].id,
|
||||
'lines': [
|
||||
('create', [{
|
||||
'description': 'Test',
|
||||
'quantity': 2,
|
||||
'unit_price': Decimal('25'),
|
||||
}]),
|
||||
],
|
||||
}])
|
||||
self.assertEqual(party.credit_amount, Decimal('100'))
|
||||
Sale.quote([sale])
|
||||
# Test limit reaches
|
||||
self.assertRaises(UserWarning, Sale.confirm, [sale])
|
||||
self.assertEqual(party.credit_amount, Decimal('100'))
|
||||
# Increase limit
|
||||
party.credit_limit_amount = Decimal('200')
|
||||
party.save()
|
||||
# process should work
|
||||
Sale.confirm([sale])
|
||||
self.assertEqual(sale.state, 'confirmed')
|
||||
self.assertEqual(party.credit_amount, Decimal('150'))
|
||||
|
||||
# Process
|
||||
Sale.process([sale])
|
||||
# Decrease limit
|
||||
party.credit_limit_amount = Decimal('100')
|
||||
party.save()
|
||||
# process should still work as sale is already processing
|
||||
Sale.process([sale])
|
||||
|
||||
# Invoice part of quantity does not change the credit amount
|
||||
invoice, = sale.invoices
|
||||
invoice_line, = invoice.lines
|
||||
invoice_line.quantity = 1
|
||||
invoice_line.save()
|
||||
Invoice.post([invoice])
|
||||
self.assertEqual(party.credit_amount, Decimal('150'))
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
15
modules/sale_credit_limit/tryton.cfg
Normal file
15
modules/sale_credit_limit/tryton.cfg
Normal file
@@ -0,0 +1,15 @@
|
||||
[tryton]
|
||||
version=7.8.1
|
||||
depends:
|
||||
ir
|
||||
account_credit_limit
|
||||
sale
|
||||
currency
|
||||
account_invoice
|
||||
company
|
||||
|
||||
[register]
|
||||
model:
|
||||
party.Party
|
||||
sale.Sale
|
||||
sale.Line
|
||||
Reference in New Issue
Block a user