1507 lines
56 KiB
Python
1507 lines
56 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
import logging
|
|
from decimal import Decimal
|
|
from datetime import date, timedelta
|
|
import calendar
|
|
from sql.aggregate import Sum
|
|
from sql.functions import Extract
|
|
from sql.conditionals import Case
|
|
from sql.operators import In
|
|
|
|
from trytond.model import fields, Unique, ModelView
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Bool, Eval, Or, Not
|
|
from trytond.report import Report
|
|
from trytond.wizard import (
|
|
Wizard, StateView, StateAction, StateReport, Button, StateTransition
|
|
)
|
|
from trytond.i18n import gettext
|
|
from .exceptions import (
|
|
SaleMissingSequenceError, ShopUserError, SaleWriteError, PartyMissingAccount
|
|
)
|
|
from trytond.modules.sale.exceptions import SaleValidationError
|
|
from trytond.model.exceptions import AccessError
|
|
from trytond.modules.product import round_price
|
|
|
|
logger = logging.getLogger(__name__)
|
|
_ZERO = Decimal('0.00')
|
|
|
|
# domain=[
|
|
# ('id', 'in', Eval('context', {}).get('shops', [])),
|
|
# ],
|
|
|
|
|
|
class Sale(metaclass=PoolMeta):
|
|
__name__ = 'sale.sale'
|
|
shop = fields.Many2One('sale.shop', 'Shop', required=True,
|
|
states={
|
|
'readonly': Or(Bool(Eval('number')), Bool(Eval('lines'))),
|
|
}, depends=['number', 'lines'])
|
|
shop_address = fields.Function(fields.Many2One('party.address',
|
|
'Shop Address'), 'on_change_with_shop_address')
|
|
source = fields.Many2One('sale.source', 'Source')
|
|
payments = fields.One2Many('account.statement.line', 'sale', 'Payments')
|
|
paid_amount = fields.Function(fields.Numeric('Paid Amount', digits=(16, 2)),
|
|
'get_paid_amount')
|
|
residual_amount = fields.Function(fields.Numeric('Residual Amount',
|
|
digits=(16, 2), readonly=True), 'get_residual_amount')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Sale, cls).__setup__()
|
|
cls._buttons.update({
|
|
'wizard_sale_payment': {
|
|
'invisible': Eval('state').in_(['done', 'transferred']),
|
|
'readonly': Not(Bool(Eval('lines'))),
|
|
},
|
|
})
|
|
cls.state_string = super(Sale, cls).state.translated('state')
|
|
t = cls.__table__()
|
|
cls._sql_constraints.extend([
|
|
('number_uniq', Unique(t, t.shop, t.number),
|
|
'There is another sale with the same number.\n'
|
|
'The number of the sale must be unique!')
|
|
])
|
|
|
|
shipment_addr_domain = cls.shipment_address.domain[:]
|
|
if shipment_addr_domain:
|
|
cls.shipment_address.domain = [
|
|
'OR',
|
|
shipment_addr_domain,
|
|
[('id', '=', Eval('shop_address', 0))],
|
|
]
|
|
else:
|
|
cls.shipment_address.domain = [('id', '=', Eval('shop_address'))]
|
|
cls.shipment_address.depends.append('shop_address')
|
|
cls.currency.states['readonly'] = Eval('state') != 'draft'
|
|
cls.currency.depends.append('shop')
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
return user.shop.company.id if user.shop else \
|
|
Transaction().context.get('company')
|
|
|
|
@fields.depends('company')
|
|
def on_change_company(self):
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
if not user.shop:
|
|
super(Sale, self).on_change_company()
|
|
else:
|
|
self.invoice_method = user.shop.sale_invoice_method
|
|
self.shipment_method = user.shop.sale_shipment_method
|
|
self.payment_term = user.shop.payment_term.id
|
|
|
|
@staticmethod
|
|
def default_shop():
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
return user.shop.id if user.shop else None
|
|
|
|
@staticmethod
|
|
def default_description():
|
|
config = Pool().get('sale.configuration')(1)
|
|
if config.default_description:
|
|
return config.default_description
|
|
|
|
@staticmethod
|
|
def default_warehouse():
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
if user.shop:
|
|
return user.shop.warehouse.id
|
|
else:
|
|
Location = Pool().get('stock.location')
|
|
return Location.search([('type', '=', 'warehouse')], limit=1)[0].id
|
|
|
|
@staticmethod
|
|
def default_price_list():
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
return user.shop.price_list.id if user.shop else None
|
|
|
|
@staticmethod
|
|
def default_shop_address():
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
return (user.shop and user.shop.address and user.shop.address.id or None)
|
|
|
|
@classmethod
|
|
def copy(cls, sales, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default['payments'] = None
|
|
default['source'] = None
|
|
return super(Sale, cls).copy(sales, default)
|
|
|
|
@fields.depends('shop', 'party')
|
|
def on_change_shop(self):
|
|
if not self.shop:
|
|
return
|
|
for fname in ('company', 'warehouse', 'currency', 'payment_term'):
|
|
fvalue = getattr(self.shop, fname)
|
|
if fvalue:
|
|
setattr(self, fname, fvalue.id)
|
|
if ((not self.party or not self.party.sale_price_list)
|
|
and self.shop.price_list):
|
|
self.price_list = self.shop.price_list.id
|
|
if self.shop.sale_invoice_method:
|
|
self.invoice_method = self.shop.sale_invoice_method
|
|
if self.shop.sale_shipment_method:
|
|
self.shipment_method = self.shop.sale_shipment_method
|
|
|
|
@fields.depends('shop')
|
|
def on_change_with_shop_address(self, name=None):
|
|
return (self.shop and self.shop.address and
|
|
self.shop.address.id or None)
|
|
|
|
@fields.depends('shop', 'party')
|
|
def on_change_party(self):
|
|
super(Sale, self).on_change_party()
|
|
if self.shop:
|
|
if not self.price_list and self.invoice_address:
|
|
self.price_list = self.shop.price_list.id
|
|
self.price_list.rec_name = self.shop.price_list.rec_name
|
|
if not self.payment_term and self.invoice_address:
|
|
self.payment_term = self.shop.payment_term.id
|
|
self.payment_term.rec_name = self.shop.payment_term.rec_name
|
|
|
|
@classmethod
|
|
def get_paid_amount(cls, sales, names):
|
|
result = {n: {s.id: Decimal(0) for s in sales} for n in names}
|
|
for name in names:
|
|
for sale in sales:
|
|
for payment in sale.payments:
|
|
result[name][sale.id] += payment.amount
|
|
result[name][sale.id] += sale.get_total_vouchers_amount()
|
|
return result
|
|
|
|
def get_residual_amount(self, name=None):
|
|
total = self.total_amount_cache
|
|
if not total:
|
|
total = self.total_amount
|
|
return (total - self.paid_amount)
|
|
|
|
@classmethod
|
|
def set_number(cls, sales):
|
|
'''
|
|
Fill the number field with the sale shop or sale config sequence
|
|
'''
|
|
pool = Pool()
|
|
Config = pool.get('sale.configuration')
|
|
User = Pool().get('res.user')
|
|
config = Config(1)
|
|
user = User(Transaction().user)
|
|
for sale in sales:
|
|
if sale.number:
|
|
continue
|
|
elif sale.shop:
|
|
if sale.total_amount >= Decimal('0'):
|
|
number = sale.shop.sale_sequence.get()
|
|
else:
|
|
if not sale.shop.sale_return_sequence:
|
|
raise SaleMissingSequenceError(
|
|
gettext('sale_shop.msg_no_return_sequence'))
|
|
number = sale.shop.sale_return_sequence.get()
|
|
elif user.shop:
|
|
number = user.shop.sale_sequence.get()
|
|
else:
|
|
number = config.sale_sequence.get()
|
|
cls.write([sale], {
|
|
'number': number,
|
|
})
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
vlist2 = []
|
|
config = Pool().get('sale.configuration')(1)
|
|
for vals in vlist:
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
vals = vals.copy()
|
|
if 'shop' not in vals:
|
|
if not user.shop:
|
|
raise ShopUserError(
|
|
gettext('sale_shop.not_sale_shop', s=user.rec_name))
|
|
vals['shop'] = user.shop.id
|
|
vlist2.append(vals)
|
|
sales = super(Sale, cls).create(vlist2)
|
|
if config.on_draft_sequence:
|
|
for sale in sales:
|
|
if sale.invoice_type == '1' or sale.invoice_type == 'C':
|
|
cls.set_number([sale])
|
|
return sales
|
|
|
|
@classmethod
|
|
@ModelView.button_action('sale_shop.wizard_sale_payment')
|
|
def wizard_sale_payment(cls, sales):
|
|
pass
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
'''
|
|
Only edit Sale users available edit in this shop
|
|
'''
|
|
User = Pool().get('res.user')
|
|
user = User(Transaction().user)
|
|
if user.id != 0:
|
|
actions = iter(args)
|
|
for sales, _ in zip(actions, actions):
|
|
for sale in sales:
|
|
if not sale.shop:
|
|
raise SaleWriteError(
|
|
gettext('sale_shop.msg_sale_not_shop'))
|
|
elif sale.shop not in user.shops:
|
|
raise SaleWriteError(
|
|
gettext('sale_shop.msg_edit_sale_by_shop'))
|
|
super(Sale, cls).write(*args)
|
|
|
|
@classmethod
|
|
def delete(cls, sales):
|
|
for sale in sales:
|
|
if sale.number:
|
|
raise AccessError(
|
|
gettext('sale.msg_sale_delete_cancel',
|
|
sale=sale.rec_name))
|
|
super(Sale, cls).delete(sales)
|
|
|
|
def create_invoice(self):
|
|
invoice = super(Sale, self).create_invoice()
|
|
print('ingresa por este valor')
|
|
if not invoice:
|
|
return
|
|
if invoice.shop and invoice.shop.tax_exempt:
|
|
for line in invoice.lines:
|
|
if not line.product.account_category.account_other_income:
|
|
raise AccessError(
|
|
gettext('sale_shop.msg_dont_account_other_income',
|
|
product=line.product.rec_name))
|
|
line.account = line.product.account_category.account_other_income
|
|
return invoice
|
|
|
|
|
|
class SaleLine(metaclass=PoolMeta):
|
|
__name__ = 'sale.line'
|
|
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price with Tax',
|
|
digits=(16, Eval('_parent_sale', {}).get('currency_digits', 2)),
|
|
states={
|
|
'invisible': Eval('type') != 'line',
|
|
'readonly': Eval('sale_state') != 'draft',
|
|
}), 'on_change_with_unit_price_w_tax', setter='set_unit_price_w_tax')
|
|
amount_w_tax = fields.Function(fields.Numeric('Amount with Tax',
|
|
digits=(16, Eval('_parent_sale', {}).get('currency_digits', 2)),
|
|
states={
|
|
'invisible': Eval('type') != 'line',
|
|
'readonly': Eval('sale_state') != 'draft',
|
|
}), 'on_change_with_amount_w_tax', setter='set_amount_w_tax')
|
|
|
|
# TODO for remove this field
|
|
unit_price_full = fields.Numeric('Unit Price Full',
|
|
digits=(16, Eval('_parent_sale', {}).get('currency_digits',
|
|
Eval('currency_digits', 2))),
|
|
states={
|
|
'invisible': Eval('type') != 'line',
|
|
'readonly': True,
|
|
},
|
|
depends=['type', 'currency_digits'])
|
|
|
|
@staticmethod
|
|
def default_currency_digits():
|
|
Company = Pool().get('company.company')
|
|
if Transaction().context.get('company'):
|
|
company = Company(Transaction().context['company'])
|
|
return company.currency.digits
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_currency():
|
|
Company = Pool().get('company.company')
|
|
if Transaction().context.get('company'):
|
|
company = Company(Transaction().context['company'])
|
|
return company.currency.id
|
|
|
|
# @classmethod
|
|
# def get_price_w_tax(cls, lines, names):
|
|
# print('ingresa')
|
|
# pool = Pool()
|
|
# Tax = pool.get('account.tax')
|
|
# Company = pool.get('company.company')
|
|
# amount_w_tax = {}
|
|
# unit_price_w_tax = {}
|
|
|
|
# def compute_amount_with_tax(line):
|
|
# tax_amount = _ZERO
|
|
# amount = _ZERO
|
|
# if line.taxes:
|
|
# tax_list = Tax.compute(line.taxes, line.unit_price or _ZERO,
|
|
# line.quantity or 0.0)
|
|
# tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
|
|
# if line.unit_price:
|
|
# amount = line.unit_price * Decimal(line.quantity)
|
|
# classification_tax = any(t.classification_tax == '02' for t in line.taxes)
|
|
# if hasattr(line.product, 'extra_tax') and line.product.extra_tax and classification_tax:
|
|
# amount += line.product.extra_tax * Decimal(line.quantity)
|
|
# return amount + tax_amount
|
|
|
|
# for line in lines:
|
|
# amount = _ZERO
|
|
# unit_price = _ZERO
|
|
# if line.sale:
|
|
# currency = line.sale.currency
|
|
# else:
|
|
# # company = Company(Transaction().context.get('company'))
|
|
# company = Company(1)
|
|
# currency = company.currency
|
|
|
|
# if line.type == 'line' and line.quantity:
|
|
# amount = compute_amount_with_tax(line)
|
|
# unit_price = amount / Decimal(str(line.quantity))
|
|
|
|
# # Only compute subtotals if the two fields are provided to speed up
|
|
# elif line.type == 'subtotal' and len(names) == 2:
|
|
# for line2 in line.sale.lines:
|
|
# if line2.type == 'line':
|
|
# amount2 = compute_amount_with_tax(line2)
|
|
# if currency:
|
|
# amount2 = currency.round(amount2)
|
|
# amount += amount2
|
|
# elif line2.type == 'subtotal':
|
|
# if line == line2:
|
|
# break
|
|
# amount = _ZERO
|
|
|
|
# if currency:
|
|
# amount = currency.round(amount)
|
|
|
|
# amount_w_tax[line.id] = amount
|
|
# unit_price_w_tax[line.id] = unit_price
|
|
|
|
# result = {
|
|
# 'amount_w_tax': amount_w_tax,
|
|
# 'unit_price_w_tax': unit_price_w_tax,
|
|
# }
|
|
|
|
# to_remove = []
|
|
# for key in list(result.keys()):
|
|
# if key not in names:
|
|
# to_remove.append(key)
|
|
# for rm in to_remove:
|
|
# del result[key]
|
|
# return result
|
|
|
|
@fields.depends('unit_price', 'quantity', 'taxes', 'product', 'currency')
|
|
def on_change_with_unit_price_w_tax(self, name=None):
|
|
if not self.quantity or self.unit_price is None:
|
|
return
|
|
currency_round = self.currency.round
|
|
if self.taxes:
|
|
pool = Pool()
|
|
Tax = pool.get('account.tax')
|
|
tax_list = Tax.compute(self.taxes, self.unit_price, 1)
|
|
tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
|
|
classification_tax = any(t.classification_tax == '02' for t in self.taxes)
|
|
extra_tax = 0
|
|
if self.product.extra_tax and classification_tax:
|
|
extra_tax = self.product.extra_tax
|
|
return currency_round(tax_amount + extra_tax + self.unit_price)
|
|
else:
|
|
return currency_round(self.unit_price)
|
|
|
|
@fields.depends(
|
|
'quantity', 'unit_price_w_tax',
|
|
methods=['on_change_with_discount_rate', 'on_change_with_discount_amount', 'on_change_with_discount'])
|
|
def on_change_unit_price_w_tax(self):
|
|
if not self.unit_price_w_tax or not self.quantity:
|
|
return
|
|
currency_round = self.currency.round
|
|
if self.taxes:
|
|
pool = Pool()
|
|
Tax = pool.get('account.tax')
|
|
res = Tax.reverse_compute(self.unit_price_w_tax, self.taxes)
|
|
self.unit_price = round_price(res)
|
|
self.amount_w_tax = currency_round(self.unit_price_w_tax * Decimal(self.quantity))
|
|
else:
|
|
self.unit_price = self.unit_price_w_tax
|
|
self.amount_w_tax = currency_round(self.unit_price_w_tax * Decimal(self.quantity))
|
|
self.discount_rate = self.on_change_with_discount_rate()
|
|
self.discount_amount = self.on_change_with_discount_amount()
|
|
self.discount = self.on_change_with_discount()
|
|
|
|
@classmethod
|
|
def set_unit_price_w_tax(cls, lines, name, value):
|
|
pass
|
|
|
|
@fields.depends('unit_price_w_tax' , 'quantity', 'product', 'currency')
|
|
def on_change_with_amount_w_tax(self, name=None):
|
|
if self.unit_price_w_tax is None:
|
|
return
|
|
currency_round = self.currency.round
|
|
return currency_round(self.unit_price_w_tax * Decimal(self.quantity))
|
|
|
|
@fields.depends('amount_w_tax', 'quantity', 'taxes',
|
|
methods=['on_change_with_discount_rate', 'on_change_with_discount_amount', 'on_change_with_discount'])
|
|
def on_change_amount_w_tax(self):
|
|
pool = Pool()
|
|
Tax = pool.get('account.tax')
|
|
currency_round = self.currency.round
|
|
if self.taxes and self.amount_w_tax is not None and self.quantity:
|
|
self.unit_price_w_tax = currency_round(self.amount_w_tax / Decimal(self.quantity))
|
|
res = Tax.reverse_compute(self.unit_price_w_tax, self.taxes)
|
|
self.unit_price = round_price(res)
|
|
self.discount_rate = self.on_change_with_discount_rate()
|
|
self.discount_amount = self.on_change_with_discount_amount()
|
|
self.discount = self.on_change_with_discount()
|
|
|
|
@classmethod
|
|
def set_amount_w_tax(cls, lines, name, value):
|
|
pass
|
|
|
|
@fields.depends(
|
|
'product', 'unit', 'quantity', 'description',
|
|
'_parent_sale.party', '_parent_sale.currency', '_parent_sale.sale_date')
|
|
def on_change_product(self):
|
|
super(SaleLine, self).on_change_product()
|
|
if self.product:
|
|
desc = self.product.template.name
|
|
if self.product.description:
|
|
desc += ' | ' + self.product.description
|
|
self.description = desc
|
|
|
|
|
|
# @fields.depends('product', 'quantity', 'unit', 'taxes',
|
|
# '_parent_sale.currency', '_parent_sale.party',
|
|
# '_parent_sale.sale_date', 'unit_price_full')
|
|
# def on_change_quantity(self):
|
|
# super(SaleLine, self).on_change_quantity()
|
|
# if self.unit_price_full:
|
|
# self.on_change_unit_price_full()
|
|
|
|
|
|
class SaleBySupplierStart(ModelView):
|
|
'Sale By Supplier Start'
|
|
__name__ = 'sale_shop.sale_by_supplier.start'
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
suppliers = fields.Many2Many('party.party', None, None, 'Suppliers')
|
|
shop = fields.Many2One('sale.shop', 'Shop')
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
detailed = fields.Boolean('Detailed')
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_end_date():
|
|
Date_ = Pool().get('ir.date')
|
|
return Date_.today()
|
|
|
|
|
|
class PrintSaleBySupplier(Wizard):
|
|
'Sale By Supplier'
|
|
__name__ = 'sale_shop.print_sale_by_supplier'
|
|
start = StateView(
|
|
'sale_shop.sale_by_supplier.start',
|
|
'sale_shop.print_sale_by_supplier_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('sale_shop.sale_by_supplier_report')
|
|
|
|
def do_print_(self, action):
|
|
suppliers = [s.id for s in self.start.suppliers]
|
|
shop_id = None
|
|
shop_name = ''
|
|
if self.start.shop:
|
|
shop_id = self.start.shop.id
|
|
shop_name = self.start.shop.name
|
|
data = {
|
|
'ids': [],
|
|
'company': self.start.company.id,
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'suppliers': suppliers,
|
|
'detailed': self.start.detailed,
|
|
'shop': shop_id,
|
|
'shop_name': shop_name,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class SaleBySupplier(Report):
|
|
'Sale By Supplier Report'
|
|
__name__ = 'sale_shop.sale_by_supplier_report'
|
|
|
|
@classmethod
|
|
def get_last_purchase(cls, product_id):
|
|
PurchaseLine = Pool().get('purchase.line')
|
|
lines = PurchaseLine.search([
|
|
('product', '=', product_id),
|
|
('purchase.state', 'in', ('processing', 'done')),
|
|
], order=[('create_date', 'DESC')], limit=1)
|
|
if lines:
|
|
line = lines[0]
|
|
return (line.purchase.purchase_date, line.quantity)
|
|
else:
|
|
return None, None
|
|
|
|
@classmethod
|
|
def get_margin(cls, pline):
|
|
res = 0
|
|
total_cost = pline['cost_price'] * pline['quantity']
|
|
if pline['amount'] != 0:
|
|
res = 1 - (total_cost / pline['amount'])
|
|
return res
|
|
|
|
@classmethod
|
|
def get_last_sales(cls, product_id, end_date, days):
|
|
InvoiceLine = Pool().get('account.invoice.line')
|
|
start_date = end_date - timedelta(days=days)
|
|
lines = InvoiceLine.search([
|
|
('product', '=', product_id),
|
|
('invoice.invoice_date', '>=', start_date),
|
|
('invoice.invoice_date', '<=', end_date),
|
|
('invoice.state', 'in', ('posted', 'paid')),
|
|
('invoice.type', '=', 'out'),
|
|
('type', '=', 'line'),
|
|
])
|
|
return sum([l.quantity for l in lines]), start_date
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Party = pool.get('party.party')
|
|
ProductSupplier = pool.get('purchase.product_supplier')
|
|
company = Company(data['company'])
|
|
cursor = Transaction().connection.cursor()
|
|
dom_suppliers = [
|
|
('template', '!=', None)
|
|
]
|
|
total_sales = []
|
|
total_sales_append = total_sales.append
|
|
parties_query = ''
|
|
start_date = data['start_date']
|
|
end_date = data['end_date']
|
|
delta = (data['end_date'] - data['start_date']).days + 1
|
|
|
|
if data['suppliers']:
|
|
dom_suppliers.append([
|
|
('party', 'in', data['suppliers']),
|
|
])
|
|
if len(data['suppliers']) == 1:
|
|
supplier = data['suppliers'][0]
|
|
parties_query = f' AND pu.party={supplier}'
|
|
else:
|
|
parties = str(tuple(data['suppliers']))
|
|
parties_query = f' AND pu.party IN {parties}'
|
|
|
|
products_suppliers = ProductSupplier.search(dom_suppliers)
|
|
products_dict = {}
|
|
for ps in products_suppliers:
|
|
if ps.template.products:
|
|
product_id = ps.template.products[0].id
|
|
products_dict[product_id] = ps.party.id
|
|
|
|
products_query = ''
|
|
if data['suppliers']:
|
|
keys = products_dict.keys()
|
|
if len(keys) == 1:
|
|
product_id = keys[0]
|
|
products_query = f' AND al.product={product_id} '
|
|
elif keys:
|
|
products_ids = str(tuple(keys))
|
|
products_query = f' AND al.product IN {products_ids} '
|
|
|
|
shops_query = ''
|
|
start_date = data['start_date']
|
|
if data['shop']:
|
|
shop = data['shop']
|
|
shops_query = f' AND ai.shop={shop} '
|
|
query = f"""
|
|
SELECT
|
|
al.product,
|
|
pp.code,
|
|
pt.name,
|
|
SUM(al.unit_price * al.quantity) AS amount,
|
|
SUM(pc.cost_price * al.quantity) AS cost,
|
|
SUM(al.quantity) AS quantity
|
|
FROM account_invoice_line AS al
|
|
JOIN account_invoice AS ai ON al.invoice=ai.id
|
|
JOIN product_product AS pp ON al.product=pp.id
|
|
JOIN product_template AS pt ON pp.template=pt.id
|
|
JOIN product_cost_price AS pc ON pc.product=pp.id
|
|
WHERE ai.invoice_date>='{start_date}' AND ai.invoice_date<='{end_date}'
|
|
AND ai.state IN ('posted', 'paid')
|
|
AND ai.type='out' AND al.type='line'
|
|
{products_query}
|
|
{shops_query}
|
|
GROUP BY al.product, pp.code, pt.name
|
|
"""
|
|
cursor.execute(query)
|
|
_records = cursor.fetchall()
|
|
|
|
def compute_margin(sales, costs):
|
|
margin = 0
|
|
if sales and sales != 0:
|
|
margin = 1 - costs / sales
|
|
return margin
|
|
|
|
# suppliers = {0: {
|
|
# 'name': 'NA',
|
|
# 'products': [],
|
|
# 'sales': [],
|
|
# 'costs': [],
|
|
# }}
|
|
suppliers = {}
|
|
|
|
def _rec2dict(rec):
|
|
return {
|
|
'code': rec[1],
|
|
'name': rec[2],
|
|
'amount': rec[3],
|
|
'quantity': rec[5],
|
|
'margin': compute_margin(rec[3], rec[4]),
|
|
'daily_sale': rec[5] / delta,
|
|
}
|
|
|
|
for rec in _records:
|
|
total_sales_append(rec[3])
|
|
pd_id = rec[0]
|
|
party_id = products_dict.get(pd_id, 0)
|
|
try:
|
|
if data['detailed']:
|
|
suppliers[party_id]['products'].append(_rec2dict(rec))
|
|
suppliers[party_id]['sales'].append(rec[3])
|
|
suppliers[party_id]['costs'].append(rec[4])
|
|
except Exception:
|
|
parties = Party.search_read([
|
|
('id', '=', int(party_id))
|
|
], fields_names=['name'])
|
|
if not parties:
|
|
continue
|
|
party = parties[0]
|
|
party_id = party['id']
|
|
suppliers[party_id] = {
|
|
'name': party['name'],
|
|
'sales': [rec[3]],
|
|
'costs': [rec[4]]
|
|
}
|
|
if data['detailed']:
|
|
suppliers[party_id]['products'] = [_rec2dict(rec)]
|
|
|
|
for key, value in suppliers.items():
|
|
suppliers[key]['sales'] = sum(value['sales'])
|
|
suppliers[key]['costs'] = sum(value['costs'])
|
|
|
|
# Maybe we need this in the future
|
|
# sales_30, date_ = cls.get_last_sales(pline['product_id'],
|
|
# data['start_date'], 30)
|
|
# sales_60, _ = cls.get_last_sales(pline['product_id'],
|
|
# date_, 60)
|
|
|
|
records = suppliers.values()
|
|
report_context['records'] = records
|
|
report_context['start_date'] = data['start_date']
|
|
report_context['end_date'] = data['end_date']
|
|
report_context['today'] = date.today()
|
|
report_context['company'] = company.party.name
|
|
report_context['shop'] = data['shop_name']
|
|
report_context['total_sales'] = sum(total_sales)
|
|
report_context['detailed'] = data['detailed']
|
|
report_context['compute_margin'] = compute_margin
|
|
return report_context
|
|
|
|
|
|
class SaleShopDetailedStart(ModelView):
|
|
'Sale Shop Detailed Start'
|
|
__name__ = 'sale_shop.sale_detailed.start'
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
salesman = fields.Many2One('company.employee', 'Salesman')
|
|
party = fields.Many2One('party.party', 'Party')
|
|
product = fields.Many2One('product.product', 'Product')
|
|
shop = fields.Many2One('sale.shop', 'Shop')
|
|
categories = fields.Many2Many('product.category', None, None, 'Categories')
|
|
# groupby_category = fields.Boolean('Group By Category')
|
|
# groupby_products = fields.Boolean('Group By Products')
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class SaleShopDetailed(Wizard):
|
|
'Sale Shop Detailed'
|
|
__name__ = 'sale_shop.sale_detailed'
|
|
start = StateView(
|
|
'sale_shop.sale_detailed.start',
|
|
'sale_shop.sale_shop_detailed_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateAction('sale_shop.report_sale_detailed')
|
|
|
|
def do_print_(self, action):
|
|
category_ids = []
|
|
salesman_id = None
|
|
party_id = None
|
|
product_id = None
|
|
shop_id = None
|
|
if self.start.salesman:
|
|
salesman_id = self.start.salesman.id
|
|
if self.start.shop:
|
|
shop_id = self.start.shop.id
|
|
if self.start.party:
|
|
party_id = self.start.party.id
|
|
if self.start.product:
|
|
product_id = self.start.product.id
|
|
if self.start.categories:
|
|
category_ids = [acc.id for acc in self.start.categories]
|
|
# if self.start.categories:
|
|
# data['categories'] = [c.id for c in self.start.categories]
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'salesman': salesman_id,
|
|
'party': party_id,
|
|
'product': product_id,
|
|
'categories': category_ids,
|
|
'shop': shop_id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class SaleShopDetailedReport(Report):
|
|
'Sale Shop Detailed Report'
|
|
__name__ = 'sale_shop.report_sale_detailed'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
InvoiceLine = pool.get('account.invoice.line')
|
|
Company = pool.get('company.company')
|
|
Category = pool.get('product.template-product.category')
|
|
|
|
categories = Category.search([('category', 'in', data['categories'])])
|
|
products_ids = [c.template.id for c in categories]
|
|
|
|
invoice_line_dom = [
|
|
('invoice.invoice_date', '>=', data['start_date']),
|
|
('invoice.invoice_date', '<=', data['end_date']),
|
|
('invoice.company', '=', data['company']),
|
|
('invoice.type', '=', 'out'),
|
|
('invoice.state', 'in', ['posted', 'paid', 'validated']),
|
|
]
|
|
if data['salesman']:
|
|
invoice_line_dom.append(
|
|
('invoice.salesman', '=', data['salesman'])
|
|
)
|
|
if data['shop']:
|
|
invoice_line_dom.append(
|
|
('invoice.shop', '=', data['shop'])
|
|
)
|
|
if data['party']:
|
|
invoice_line_dom.append(
|
|
('invoice.party', '=', data['party'])
|
|
)
|
|
if data['product']:
|
|
invoice_line_dom.append(
|
|
('product', '=', data['product'])
|
|
)
|
|
if data['categories']:
|
|
if products_ids:
|
|
invoice_line_dom.append(
|
|
('product.template', 'in', products_ids)
|
|
)
|
|
else:
|
|
invoice_line_dom.append(
|
|
('product.template.account_category', 'in', data['categories'])
|
|
)
|
|
# if data['categories']:
|
|
# invoice_line_dom.append(
|
|
# ('product.categories', 'in', data['categories'])
|
|
# )
|
|
|
|
config = pool.get('sale.configuration')(1)
|
|
products_exception = []
|
|
if hasattr(config, 'tip_product') and config.tip_product and config.exclude_tip_and_delivery:
|
|
products_exception.append(config.tip_product.id)
|
|
if hasattr(config, 'delivery_product') and config.delivery_product and config.exclude_tip_and_delivery:
|
|
products_exception.append(config.delivery_product.id)
|
|
|
|
if len(products_exception) > 0:
|
|
invoice_line_dom.append(('product', 'not in', products_exception))
|
|
|
|
start_lines = InvoiceLine.search(
|
|
invoice_line_dom, order=[('invoice.invoice_date', 'ASC')]
|
|
)
|
|
lines = []
|
|
amount = []
|
|
# amount_w_tax = []
|
|
for line in start_lines:
|
|
if line.type == 'line' and not line.product:
|
|
raise SaleValidationError(
|
|
gettext('sale_shop.msg_sale_not_product', s=line.sale.number
|
|
))
|
|
|
|
amount.append(line.amount)
|
|
lines.append(line)
|
|
# is_invoiced = False
|
|
# if line.invoice:
|
|
# for inv in line.sale.invoices:
|
|
# if line.invoice.state not in ['draft', 'cancel']:
|
|
# is_invoiced = True
|
|
# if is_invoiced:
|
|
# lines.append(line)
|
|
# # amount_w_tax.append(line.amount_w_tax)
|
|
# else:
|
|
# amount.append(line.amount)
|
|
# # amount_w_tax.append(line.amount_w_tax)
|
|
|
|
# report_context['amount_w_tax'] = sum(amount_w_tax)
|
|
report_context['amount'] = sum(amount)
|
|
report_context['records'] = lines
|
|
report_context['company'] = Company(data['company'])
|
|
return report_context
|
|
|
|
|
|
class SaleBySellerStart(ModelView):
|
|
'Sale By Seller Start'
|
|
__name__ = 'sale_shop.sale_by_seller.start'
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
shop = fields.Many2One('sale.shop', 'Shop', depends=['company'],
|
|
domain=[
|
|
('company', '=', Eval('company'))
|
|
])
|
|
salesmans = fields.Many2Many(
|
|
'company.employee', None, None, 'Salesmans')
|
|
payment_methods = fields.Many2Many(
|
|
'account.invoice.payment_term', None, None, 'Payment Method')
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class SaleBySeller(Wizard):
|
|
'Sale By Seller'
|
|
__name__ = 'sale_shop.sale_by_seller.wizard'
|
|
start = StateView(
|
|
'sale_shop.sale_by_seller.start',
|
|
'sale_shop.sale_by_seller_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateAction('sale_shop.sale_by_seller_report_')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'ids': [],
|
|
'company': self.start.company.id,
|
|
'company_name': self.start.company.rec_name,
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'shop': None,
|
|
'shop_name': None,
|
|
'payment_terms': None,
|
|
'salesmans': None,
|
|
}
|
|
shop = self.start.shop
|
|
if shop:
|
|
data['shop'] = self.start.shop.id
|
|
data['shop_name'] = self.start.shop.rec_name
|
|
if self.start.payment_methods:
|
|
data['payment_terms'] = [s.id for s in self.start.payment_methods]
|
|
if self.start.salesmans:
|
|
data['salesmans'] = [s.id for s in self.start.salesmans]
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class SaleBySellerReport(Report):
|
|
__name__ = 'sale_shop.sale_by_seller_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
Company = pool.get('company.company')
|
|
shop = data['shop']
|
|
invoice_dom = [
|
|
('invoice_date', '>=', data['start_date']),
|
|
('invoice_date', '<=', data['end_date']),
|
|
('company', '=', data['company']),
|
|
('state', 'in', ['posted', 'paid']),
|
|
('type', '=', 'out'),
|
|
]
|
|
if shop:
|
|
invoice_dom.append(
|
|
('shop', '=', shop),
|
|
)
|
|
if data['payment_terms']:
|
|
invoice_dom.append(('payment_term', 'in', data['payment_terms']))
|
|
if data['salesmans']:
|
|
invoice_dom.append(('salesman', 'in', data['salesmans']))
|
|
invoices = Invoice.search(invoice_dom)
|
|
lines = {}
|
|
sum_total_amount = 0
|
|
sum_total_cash = 0
|
|
sum_total_credit = 0
|
|
for line in invoices:
|
|
seller = line.salesman
|
|
if seller:
|
|
cash = 0
|
|
credit = 0
|
|
seller_id = line.salesman.id
|
|
payment_term_type = line.payment_term.payment_type
|
|
amount = line.untaxed_amount
|
|
if payment_term_type == '1':
|
|
cash = amount
|
|
else:
|
|
credit = amount
|
|
if seller_id not in lines.keys():
|
|
lines[seller_id] = {
|
|
'shop': line.shop.rec_name,
|
|
'seller': line.salesman.rec_name,
|
|
'cash': cash,
|
|
'credit': credit,
|
|
'amount': amount,
|
|
}
|
|
sum_total_amount += amount
|
|
sum_total_cash += cash
|
|
sum_total_credit += credit
|
|
else:
|
|
lines[seller_id]['cash'] += cash
|
|
lines[seller_id]['credit'] += credit
|
|
lines[seller_id]['amount'] += amount
|
|
sum_total_amount += amount
|
|
sum_total_cash += cash
|
|
sum_total_credit += credit
|
|
|
|
report_context['records'] = lines.values()
|
|
report_context['sum_total_amount'] = sum_total_amount
|
|
report_context['sum_total_cash'] = sum_total_cash
|
|
report_context['sum_total_credit'] = sum_total_credit
|
|
report_context['company'] = Company(data['company'])
|
|
report_context['company_name'] = data['company_name']
|
|
return report_context
|
|
|
|
|
|
class SaleMonthByShopStart(ModelView):
|
|
'Sale Month By Shop Start'
|
|
__name__ = 'sale_shop.sale_month_shop.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
shop = fields.Many2One('sale.shop', 'Shop', required=True,
|
|
depends=['company'], domain=[
|
|
('company', '=', Eval('company'))
|
|
])
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
period = fields.Many2One('account.period', 'Period',
|
|
depends=['fiscalyear'], required=True, domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
])
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@fields.depends('fiscalyear')
|
|
def on_change_fiscalyear(self):
|
|
self.period = None
|
|
|
|
|
|
class SaleMonthByShop(Wizard):
|
|
'Sale MonthByShop'
|
|
__name__ = 'sale_shop.sale_month_shop'
|
|
start = StateView(
|
|
'sale_shop.sale_month_shop.start',
|
|
'sale_shop.sale_month_shop_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('sale_shop.sale_month_shop_report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'shop': self.start.shop.id,
|
|
'period': self.start.period.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class SaleMonthByShopReport(Report):
|
|
'Sale Month By Shop'
|
|
__name__ = 'sale_shop.sale_month_shop_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
company = Company(data['company'])
|
|
Invoice = pool.get('account.invoice')
|
|
Line = pool.get('account.invoice.line')
|
|
Payment_term = pool.get('account.invoice.payment_term')
|
|
Period = pool.get('account.period')
|
|
Shop = pool.get('sale.shop')
|
|
period = Period(data['period'])
|
|
|
|
config = pool.get('sale.configuration')(1)
|
|
products_exception = []
|
|
if hasattr(config, 'tip_product') and config.tip_product and config.exclude_tip_and_delivery:
|
|
products_exception.append(config.tip_product.id)
|
|
if hasattr(config, 'delivery_product') and config.delivery_product and config.exclude_tip_and_delivery:
|
|
products_exception.append(config.delivery_product.id)
|
|
|
|
totals = {}
|
|
if len(products_exception) > 0:
|
|
invoice = Invoice.__table__()
|
|
line = Line.__table__()
|
|
payment_term = Payment_term.__table__()
|
|
|
|
where = invoice.state.in_(['posted', 'paid', 'validated'])
|
|
where &= invoice.invoice_date >= period.start_date
|
|
where &= In(line.product, products_exception)
|
|
where &= invoice.invoice_date <= period.end_date
|
|
where &= invoice.shop == data['shop']
|
|
where &= invoice.company == data['company']
|
|
where &= invoice.type == 'out'
|
|
|
|
cursor = Transaction().connection.cursor()
|
|
cursor.execute(*line.join(
|
|
invoice, condition=line.invoice == invoice.id).join(
|
|
payment_term, condition=invoice.payment_term == payment_term.id).select(
|
|
Extract('DAY', invoice.invoice_date).as_('Day'),
|
|
Sum(Case((payment_term.payment_type == '1', line.quantity*line.unit_price), else_=0)).as_('amount_cash'),
|
|
Sum(Case((payment_term.payment_type != '1', line.quantity*line.unit_price), else_=0)).as_('amount_credit'),
|
|
Sum(line.quantity*line.unit_price).as_('amount'),
|
|
where=where,
|
|
group_by=(Extract('DAY', invoice.invoice_date))
|
|
))
|
|
|
|
result = cursor.fetchall()
|
|
columns = list(cursor.description)
|
|
for row in result:
|
|
row_dict = {}
|
|
for i, col in enumerate(columns):
|
|
row_dict[col.name] = row[i]
|
|
totals[int(row[0])] = row_dict
|
|
|
|
invoices = Invoice.search([
|
|
('company', '=', data['company']),
|
|
('shop', '=', data['shop']),
|
|
('invoice_date', '>=', period.start_date),
|
|
('invoice_date', '<=', period.end_date),
|
|
('type', '=', 'out'),
|
|
('state', 'in', ['posted', 'paid', 'validated']),
|
|
], order=[('invoice_date', 'ASC')])
|
|
year = period.start_date.year
|
|
month = period.start_date.month
|
|
_, last_day = calendar.monthrange(year, period.start_date.month)
|
|
|
|
mdays = {(nday + 1): {
|
|
'date': date(year, month, nday + 1),
|
|
'num_invoices': [],
|
|
'untaxed_amount': [],
|
|
'tax_amount': [],
|
|
'total_amount': [],
|
|
'cash': [],
|
|
'credit': []
|
|
} for nday in range(last_day)}
|
|
|
|
sum_untaxed_amount = []
|
|
sum_total_cash = []
|
|
sum_total_credit = []
|
|
sum_tax_amount = []
|
|
sum_total_amount = []
|
|
|
|
def _is_credit(payment_term):
|
|
res = []
|
|
for line in payment_term.lines:
|
|
res.append(sum([d.months + d.weeks + d.days
|
|
for d in line.relativedeltas]))
|
|
if sum(res) > 0:
|
|
return True
|
|
return False
|
|
|
|
for invoice in invoices:
|
|
if _is_credit(invoice.payment_term):
|
|
mdays[invoice.invoice_date.day]['credit'].append(invoice.total_amount)
|
|
else:
|
|
mdays[invoice.invoice_date.day]['cash'].append(invoice.total_amount)
|
|
mdays[invoice.invoice_date.day]['num_invoices'].append(1)
|
|
mdays[invoice.invoice_date.day]['untaxed_amount'].append(invoice.untaxed_amount)
|
|
mdays[invoice.invoice_date.day]['tax_amount'].append(invoice.tax_amount)
|
|
mdays[invoice.invoice_date.day]['total_amount'].append(invoice.total_amount)
|
|
for k, v in mdays.items():
|
|
if k in totals.keys():
|
|
mdays.update({k: {
|
|
'date': mdays[k]['date'],
|
|
'num_invoices': sum(mdays[k]['num_invoices']),
|
|
'untaxed_amount': sum(mdays[k]['untaxed_amount']) - Decimal(totals[k]['amount']),
|
|
'tax_amount': sum(mdays[k]['tax_amount']),
|
|
'total_amount': sum(mdays[k]['total_amount']) - Decimal(totals[k]['amount']),
|
|
'cash': sum(mdays[k]['cash']) - Decimal(totals[k]['amount_cash']),
|
|
'credit': sum(mdays[k]['credit']) - Decimal(totals[k]['amount_credit'])
|
|
}
|
|
})
|
|
else:
|
|
mdays.update({k: {
|
|
'date': mdays[k]['date'],
|
|
'num_invoices': sum(mdays[k]['num_invoices']),
|
|
'untaxed_amount': sum(mdays[k]['untaxed_amount']),
|
|
'tax_amount': sum(mdays[k]['tax_amount']),
|
|
'total_amount': sum(mdays[k]['total_amount']),
|
|
'cash': sum(mdays[k]['cash']),
|
|
'credit': sum(mdays[k]['credit'])
|
|
}
|
|
})
|
|
sum_total_cash.append(mdays[k]['cash'])
|
|
sum_total_credit.append(mdays[k]['credit'])
|
|
sum_untaxed_amount.append(mdays[k]['untaxed_amount'])
|
|
sum_tax_amount.append(mdays[k]['tax_amount'])
|
|
sum_total_amount.append(mdays[k]['total_amount'])
|
|
|
|
report_context['records'] = mdays.values()
|
|
report_context['total_credit'] = sum(sum_total_credit)
|
|
report_context['total_cash'] = sum(sum_total_cash)
|
|
report_context['untaxed_amount'] = sum(sum_untaxed_amount)
|
|
report_context['tax_amount'] = sum(sum_tax_amount)
|
|
report_context['total_amount'] = sum(sum_total_amount)
|
|
report_context['shop'] = Shop(data['shop']).name
|
|
report_context['period'] = period.name
|
|
report_context['company'] = company.party.name
|
|
|
|
return report_context
|
|
|
|
|
|
class SalePaymentForm(ModelView):
|
|
'Sale Payment Form'
|
|
__name__ = 'sale.payment.form'
|
|
statement = fields.Many2One('account.statement', 'Statement',
|
|
required=True)
|
|
amount_payable = fields.Numeric('Amount Payable', required=True,
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
depends=['currency_digits'])
|
|
currency_digits = fields.Integer('Currency Digits')
|
|
party = fields.Many2One('party.party', 'Party', readonly=True)
|
|
require_voucher = fields.Boolean('Require Voucher',
|
|
depends=['statement'])
|
|
voucher = fields.Char('Voucher Number', states={
|
|
'required': Eval('require_voucher', False),
|
|
'invisible': Not(Eval('require_voucher', False)),
|
|
}, depends=['require_voucher'])
|
|
self_pick_up = fields.Boolean('Self Pick Up', readonly=True)
|
|
do_invoice = fields.Boolean('Do Invoice')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(SalePaymentForm, cls).__setup__()
|
|
|
|
@fields.depends('statement', 'voucher')
|
|
def on_change_with_require_voucher(self):
|
|
if self.statement:
|
|
return self.statement.journal.require_voucher
|
|
else:
|
|
return False
|
|
|
|
@classmethod
|
|
def default_require_voucher(cls):
|
|
return False
|
|
|
|
@classmethod
|
|
def default_do_invoice(cls):
|
|
return True
|
|
|
|
|
|
class WizardSalePayment(Wizard):
|
|
'Wizard Sale Payment'
|
|
__name__ = 'sale.payment'
|
|
start = StateView(
|
|
'sale.payment.form',
|
|
'sale_shop.sale_payment_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Pay', 'pay_', 'tryton-ok', default=True),
|
|
])
|
|
pay_ = StateTransition()
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(WizardSalePayment, cls).__setup__()
|
|
|
|
@classmethod
|
|
def execute(cls, session_id, data, state_name):
|
|
pool = Pool()
|
|
Sale = pool.get('sale.sale')
|
|
sale = Sale(Transaction().context['active_id'])
|
|
result = super().execute(session_id, data, state_name)
|
|
dom_statement = [
|
|
('state', '=', 'draft')
|
|
]
|
|
context = Transaction().context
|
|
|
|
if context.get('sale_device', None) and sale.sale_device:
|
|
dom_statement.append(('sale_device', '=', sale.sale_device.id))
|
|
|
|
if result.get('view'):
|
|
result['view']['fields_view']['fields']['statement']['domain'] = dom_statement
|
|
return result
|
|
|
|
def default_start(self, fields):
|
|
pool = Pool()
|
|
Sale = pool.get('sale.sale')
|
|
sale = Sale(Transaction().context['active_id'])
|
|
return {
|
|
'amount_payable': abs(sale.total_amount - sale.paid_amount
|
|
if sale.paid_amount else sale.total_amount),
|
|
'currency_digits': sale.currency_digits,
|
|
'party': sale.party.id,
|
|
}
|
|
|
|
def transition_pay_(self):
|
|
pool = Pool()
|
|
Sale = pool.get('sale.sale')
|
|
StatementLine = pool.get('account.statement.line')
|
|
active_id = Transaction().context.get('active_id', False)
|
|
sale = Sale(active_id)
|
|
form = self.start
|
|
if form.amount_payable > 0:
|
|
if not sale.number:
|
|
Sale.set_number([sale])
|
|
|
|
if not sale.party.account_receivable:
|
|
raise PartyMissingAccount(
|
|
gettext('sale_shop.msg_party_without_account_receivable', s=sale.party.name))
|
|
account = sale.party.account_receivable.id
|
|
|
|
if form.amount_payable:
|
|
amount = form.amount_payable
|
|
if sale.total_amount < 0:
|
|
amount = amount * -1
|
|
st_line = StatementLine(
|
|
statement=form.statement.id,
|
|
date=date.today(),
|
|
amount=amount,
|
|
party=sale.party.id,
|
|
account=account,
|
|
description=self.start.voucher,
|
|
sale=active_id,
|
|
number=sale.number,
|
|
)
|
|
st_line.save()
|
|
st_line.create_move()
|
|
|
|
if sale.total_amount != sale.paid_amount:
|
|
return 'start'
|
|
sale.save()
|
|
# Overload this method in sale pos
|
|
# if self.start.do_invoice:
|
|
# # for inv in sale.invoices:
|
|
# # if inv.state == 'posted':
|
|
# # inv.write([inv], {'state': 'draft'})
|
|
# Sale.workflow_to_end([sale])
|
|
return 'end'
|
|
|
|
|
|
class MultiplePaymentSaleStart(ModelView):
|
|
'Multiple Payment Sale Start'
|
|
__name__ = 'multiple_payment_sale.start'
|
|
|
|
statement = fields.Many2One('account.statement', 'Statement',
|
|
required=True, domain=[('state', '=', 'draft')])
|
|
party = fields.Many2One('party.party', 'Party', required=True)
|
|
amount = fields.Numeric('Amount', digits=(16, 2))
|
|
sales = fields.One2Many('select_multiple_payment_sale', 'line', 'Sales',
|
|
context={'party': Eval('party')})
|
|
balance = fields.Numeric('Balance', digits=(16, 2), readonly=True)
|
|
id = fields.Integer('Id', readonly=True)
|
|
company = fields.Many2One('company.company', 'Company', readonly=True)
|
|
description = fields.Char('Description')
|
|
|
|
@staticmethod
|
|
def default_id():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_amount():
|
|
return Decimal(0)
|
|
|
|
@staticmethod
|
|
def default_balance():
|
|
return Decimal(0)
|
|
|
|
@fields.depends('sales', 'amount')
|
|
def on_change_sales(self):
|
|
if self.amount:
|
|
self.balance = self.amount - sum(s.amount_to_pay for s in self.sales if s.amount_to_pay)
|
|
|
|
@fields.depends('sales', 'amount')
|
|
def on_change_amount(self):
|
|
if self.amount and self.sales:
|
|
self.balance = self.amount - sum(s.amount_to_pay for s in self.sales if s.amount_to_pay)
|
|
|
|
|
|
class SelectMultiplePaymentSale(ModelView):
|
|
'Select Multiple Payment Sale'
|
|
__name__ = 'select_multiple_payment_sale'
|
|
|
|
line = fields.Many2One('multiple_payment_sale.start', 'Line Id')
|
|
sale = fields.Many2One('sale.sale', 'Sale', domain=[
|
|
('state', 'in', ['confirmed', 'processing']),
|
|
('party', '=', Eval('party')),
|
|
], depends=['party'])
|
|
residual_amount = fields.Numeric('residual amount', digits=(16, 2),
|
|
readonly=True)
|
|
amount_to_pay =fields.Numeric('amount to pay', digits=(16, 2),
|
|
help="Amount to pay must be less than balance")
|
|
party = fields.Many2One('party.party', 'Party')
|
|
company = fields.Many2One('company.company', 'Company', readonly=True)
|
|
|
|
@staticmethod
|
|
def default_line():
|
|
return 1
|
|
|
|
@fields.depends(
|
|
'line', '_parent_line.party', '_parent_line.company')
|
|
def on_change_line(self):
|
|
if self.line:
|
|
self.party = self.line.party
|
|
self.company = self.line.company
|
|
|
|
@fields.depends('residual_amount', 'amount_to_pay',
|
|
'sale', 'line', '_parent_line.balance')
|
|
def on_change_sale(self):
|
|
if self.sale:
|
|
residual = self.sale.residual_amount
|
|
self.residual_amount = residual
|
|
self.amount_to_pay = residual
|
|
else:
|
|
self.residual_amount = None
|
|
self.amount_to_pay = None
|
|
|
|
@fields.depends('amount_to_pay', 'line', 'residual_amount')
|
|
def on_change_amount_to_pay(self):
|
|
if self.amount_to_pay > self.line.balance or self.amount_to_pay > self.residual_amount:
|
|
self.amount_to_pay = None
|
|
|
|
|
|
class MultiplePaymentSale(Wizard):
|
|
'Multiple Payment Sale'
|
|
__name__ = 'multiple_payment_sale'
|
|
start = StateView(
|
|
'multiple_payment_sale.start',
|
|
'sale_shop.multiple_payment_sale_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Create Moves', 'create_moves', 'tryton-ok', default=True),
|
|
Button('Select Sales', 'select_sales_ask'),
|
|
])
|
|
select_sales_ask = StateView(
|
|
'sale.shop.select_sales.ask',
|
|
'sale_shop.select_sales_ask_view_form', [
|
|
Button('Add', 'add_sales', 'tryton-ok', default=True),
|
|
])
|
|
create_moves = StateTransition()
|
|
add_sales = StateTransition()
|
|
|
|
def default_select_sales_ask(self, fields):
|
|
return {
|
|
'party': self.start.party.id,
|
|
'statement': self.start.statement.id
|
|
}
|
|
|
|
def default_start(self, fields):
|
|
default = {}
|
|
if hasattr(self.select_sales_ask, 'party'):
|
|
default.update({'party': self.select_sales_ask.party.id})
|
|
if hasattr(self.select_sales_ask, 'statement'):
|
|
default.update({'statement': self.select_sales_ask.statement.id})
|
|
if hasattr(self.select_sales_ask, 'sales'):
|
|
default.update(
|
|
{'sales': [
|
|
{'sale': s.id, 'party': self.select_sales_ask.party.id} for s in self.select_sales_ask.sales
|
|
]})
|
|
return default
|
|
|
|
@classmethod
|
|
def execute(cls, session_id, data, state_name):
|
|
result = super().execute(session_id, data, state_name)
|
|
if result.get('view') and state_name == 'select_sales_ask':
|
|
sale_domain = [
|
|
('state', 'in', ['confirmed', 'processing']),
|
|
('invoice_state', '!=', 'paid'),
|
|
]
|
|
if result['view']['defaults'].get('party'):
|
|
sale_domain.append(('party', '=', result['view']['defaults']['party']))
|
|
result['view']['fields_view']['fields']['sales']['domain']= sale_domain
|
|
return result
|
|
|
|
def transition_create_moves(self):
|
|
pool = Pool()
|
|
StatementLine = pool.get('account.statement.line')
|
|
statement = self.start.statement
|
|
description = self.start.description
|
|
party = self.start.party
|
|
account = party.account_receivable
|
|
for line in self.start.sales:
|
|
st_line = StatementLine(
|
|
statement=statement,
|
|
amount=line.amount_to_pay,
|
|
party=party,
|
|
sale=line.sale,
|
|
account=account,
|
|
description=description,
|
|
date=date.today(),
|
|
number=line.sale.number,
|
|
)
|
|
st_line.save()
|
|
return 'end'
|
|
|
|
def transition_add_sales(self):
|
|
return 'start'
|
|
|
|
|
|
class SelectSalesAsk(ModelView):
|
|
'Select Sales Ask'
|
|
__name__ = 'sale.shop.select_sales.ask'
|
|
sales = fields.Many2Many('sale.sale', None, None, 'Sales')
|
|
party = fields.Many2One('party.party', 'Party', readonly=True,
|
|
states={'invisible': True})
|
|
statement = fields.Many2One('account.statement', 'Statement',
|
|
readonly=True, states={'invisible': True})
|