You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
23 KiB
503 lines
23 KiB
# -*- coding: utf-8 -*-
|
|
|
|
import logging
|
|
|
|
from openerp import SUPERUSER_ID
|
|
from openerp.osv import fields, osv
|
|
from openerp.tools.translate import _
|
|
from openerp.exceptions import UserError
|
|
import openerp.addons.decimal_precision as dp
|
|
|
|
from openerp import api, models, fields as Fields
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class NewPosOrder(osv.osv):
|
|
_inherit = "pos.order"
|
|
|
|
discount_total = Fields.Float(string='Total Discount(Fixed)', default=0.0)
|
|
discount_percent = Fields.Float(string='Total Discount(%)', default=0.0)
|
|
|
|
_defaults = {
|
|
'discount_total': lambda *a: 0.0,
|
|
'discount_percent': lambda *a: 0.0,
|
|
}
|
|
|
|
@api.onchange('discount_percent')
|
|
def change_discount_fixed(self):
|
|
if self.discount_percent:
|
|
self.discount_total = 0.0
|
|
|
|
@api.onchange('discount_total')
|
|
def change_discount_percent(self):
|
|
if self.discount_total:
|
|
self.discount_percent = 0.0
|
|
|
|
@api.depends('statement_ids', 'lines.price_subtotal_incl', 'lines.discount', 'lines.discount_fixed','discount_total','discount_percent')
|
|
def _compute_amount_all(self):
|
|
super(NewPosOrder, self)._compute_amount_all()
|
|
|
|
for order in self:
|
|
|
|
currency = order.pricelist_id.currency_id
|
|
amount_untaxed = currency.round(sum(line.price_subtotal for line in order.lines))
|
|
|
|
order.amount_total = order.amount_tax + amount_untaxed
|
|
if order.discount_total > 0:
|
|
order.amount_total -= order.discount_total
|
|
if order.discount_percent > 0:
|
|
order.amount_total -= ((order.amount_tax + amount_untaxed) * order.discount_percent / 100)
|
|
|
|
def _order_fields(self, cr, uid, ui_order, context=None):
|
|
new_discount = super(NewPosOrder, self)._order_fields(cr, uid, ui_order)
|
|
new_discount['discount_total'] = ui_order['discount_total']
|
|
new_discount['discount_percent'] = ui_order['discount_percent']
|
|
return new_discount
|
|
|
|
def action_invoice(self, cr, uid, ids, context=None):
|
|
|
|
inv_ref = self.pool.get('account.invoice')
|
|
inv_line_ref = self.pool.get('account.invoice.line')
|
|
product_obj = self.pool.get('product.product')
|
|
inv_ids = []
|
|
|
|
for order in self.pool.get('pos.order').browse(cr, uid, ids, context=context):
|
|
# Force company for all SUPERUSER_ID action
|
|
company_id = order.company_id.id
|
|
local_context = dict(context or {}, force_company=company_id, company_id=company_id)
|
|
if order.invoice_id:
|
|
inv_ids.append(order.invoice_id.id)
|
|
continue
|
|
|
|
if not order.partner_id:
|
|
raise UserError(_('Please provide a partner for the sale.'))
|
|
|
|
acc = order.partner_id.property_account_receivable_id.id
|
|
|
|
# =============adding 'discount_total' to invoice================================================================
|
|
inv = {
|
|
'name': order.name,
|
|
'origin': order.name,
|
|
'account_id': acc,
|
|
'journal_id': order.sale_journal.id or None,
|
|
'type': 'out_invoice',
|
|
'reference': order.name,
|
|
'partner_id': order.partner_id.id,
|
|
'comment': order.note or '',
|
|
'currency_id': order.pricelist_id.currency_id.id, # considering partner's sale pricelist's currency
|
|
'company_id': company_id,
|
|
'user_id': uid,
|
|
'discount_total': order.discount_total,
|
|
'discount_percent': order.discount_percent,
|
|
|
|
}
|
|
invoice = inv_ref.new(cr, uid, inv)
|
|
invoice._onchange_partner_id()
|
|
invoice.fiscal_position_id = order.fiscal_position_id
|
|
|
|
inv = invoice._convert_to_write(invoice._cache)
|
|
if not inv.get('account_id', None):
|
|
inv['account_id'] = acc
|
|
inv_id = inv_ref.create(cr, SUPERUSER_ID, inv, context=local_context)
|
|
|
|
self.write(cr, uid, [order.id], {'invoice_id': inv_id, 'state': 'invoiced'}, context=local_context)
|
|
inv_ids.append(inv_id)
|
|
for line in order.lines:
|
|
inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=local_context)[0][1]
|
|
|
|
# ===============adding 'discount fixed' to invoice lines==========================================
|
|
inv_line = {
|
|
'invoice_id': inv_id,
|
|
'product_id': line.product_id.id,
|
|
'quantity': line.qty,
|
|
'account_analytic_id': self._prepare_analytic_account(cr, uid, line, context=local_context),
|
|
'name': inv_name,
|
|
'discount_fixed': line.discount_fixed,
|
|
}
|
|
|
|
#Oldlin trick
|
|
invoice_line = inv_line_ref.new(cr, SUPERUSER_ID, inv_line, context=local_context)
|
|
invoice_line._onchange_product_id()
|
|
invoice_line.invoice_line_tax_ids = [tax.id for tax in invoice_line.invoice_line_tax_ids if tax.company_id.id == company_id]
|
|
fiscal_position_id = line.order_id.fiscal_position_id
|
|
if fiscal_position_id:
|
|
invoice_line.invoice_line_tax_ids = fiscal_position_id.map_tax(invoice_line.invoice_line_tax_ids)
|
|
invoice_line.invoice_line_tax_ids = [tax.id for tax in invoice_line.invoice_line_tax_ids]
|
|
# We convert a new id object back to a dictionary to write to bridge between old and new api
|
|
inv_line = invoice_line._convert_to_write(invoice_line._cache)
|
|
inv_line.update(price_unit=line.price_unit, discount=line.discount, discount_fixed=line.discount_fixed)
|
|
inv_line_ref.create(cr, SUPERUSER_ID, inv_line, context=local_context)
|
|
inv_ref.compute_taxes(cr, SUPERUSER_ID, [inv_id], context=local_context)
|
|
self.signal_workflow(cr, uid, [order.id], 'invoice')
|
|
inv_ref.signal_workflow(cr, SUPERUSER_ID, [inv_id], 'validate')
|
|
|
|
if not inv_ids: return {}
|
|
|
|
mod_obj = self.pool.get('ir.model.data')
|
|
res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
|
|
res_id = res and res[1] or False
|
|
return {
|
|
'name': _('Customer Invoice'),
|
|
'view_type': 'form',
|
|
'view_mode': 'form',
|
|
'view_id': [res_id],
|
|
'res_model': 'account.invoice',
|
|
'context': "{'type':'out_invoice'}",
|
|
'type': 'ir.actions.act_window',
|
|
'target': 'current',
|
|
'res_id': inv_ids and inv_ids[0] or False,
|
|
}
|
|
|
|
def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None):
|
|
# Tricky, via the workflow, we only have one id in the ids variable
|
|
"""Create a account move line of order grouped by products or not."""
|
|
account_move_obj = self.pool.get('account.move')
|
|
account_tax_obj = self.pool.get('account.tax')
|
|
property_obj = self.pool.get('ir.property')
|
|
cur_obj = self.pool.get('res.currency')
|
|
|
|
# session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context))
|
|
|
|
if session and not all(
|
|
session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)):
|
|
raise UserError(_('Selected orders do not have the same session!'))
|
|
|
|
grouped_data = {}
|
|
have_to_group_by = session and session.config_id.group_by or False
|
|
rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method
|
|
|
|
for order in self.browse(cr, uid, ids, context=context):
|
|
if order.account_move:
|
|
continue
|
|
if order.state != 'paid':
|
|
continue
|
|
|
|
current_company = order.sale_journal.company_id
|
|
|
|
group_tax = {}
|
|
account_def = property_obj.get(cr, uid, 'property_account_receivable_id', 'res.partner',
|
|
context=context)
|
|
|
|
order_account = order.partner_id and \
|
|
order.partner_id.property_account_receivable_id and \
|
|
order.partner_id.property_account_receivable_id.id or \
|
|
account_def and account_def.id
|
|
|
|
if move_id is None:
|
|
# Create an entry for the sale
|
|
# FORWARD-PORT UP TO SAAS-12
|
|
journal_id = self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID,
|
|
'pos.closing.journal_id_%s' % (
|
|
current_company.id),
|
|
default=order.sale_journal.id,
|
|
context=context)
|
|
move_id = self._create_account_move(cr, uid, order.session_id.start_at, order.name, int(journal_id),
|
|
order.company_id.id, context=context)
|
|
|
|
move = account_move_obj.browse(cr, SUPERUSER_ID, move_id, context=context)
|
|
|
|
def insert_data(data_type, values):
|
|
# if have_to_group_by:
|
|
|
|
# 'quantity': line.qty,
|
|
# 'product_id': line.product_id.id,
|
|
values.update({
|
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(
|
|
order.partner_id).id or False,
|
|
'move_id': move_id,
|
|
})
|
|
|
|
if data_type == 'product':
|
|
key = ('product', values['partner_id'],
|
|
(values['product_id'], tuple(values['tax_ids'][0][2]), values['name']),
|
|
values['analytic_account_id'], values['debit'] > 0)
|
|
elif data_type == 'tax':
|
|
key = ('tax', values['partner_id'], values['tax_line_id'], values['debit'] > 0)
|
|
elif data_type == 'counter_part':
|
|
key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0)
|
|
else:
|
|
return
|
|
|
|
grouped_data.setdefault(key, [])
|
|
|
|
# if not have_to_group_by or (not grouped_data[key]):
|
|
# grouped_data[key].append(values)
|
|
# else:
|
|
# pass
|
|
|
|
if have_to_group_by:
|
|
if not grouped_data[key]:
|
|
grouped_data[key].append(values)
|
|
else:
|
|
for line in grouped_data[key]:
|
|
if line.get('tax_code_id') == values.get('tax_code_id'):
|
|
current_value = line
|
|
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get(
|
|
'quantity', 0.0)
|
|
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit',
|
|
0.0)
|
|
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
|
|
break
|
|
else:
|
|
grouped_data[key].append(values)
|
|
else:
|
|
grouped_data[key].append(values)
|
|
|
|
# because of the weird way the pos order is written, we need to make sure there is at least one line,
|
|
# because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
|
|
# are set inside the for loop)
|
|
# TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
|
|
assert order.lines, _('The POS order must have lines when calling this method')
|
|
# Create an move for each order line
|
|
|
|
cur = order.pricelist_id.currency_id
|
|
for line in order.lines:
|
|
amount = line.price_subtotal
|
|
|
|
# Search for the income account
|
|
if line.product_id.property_account_income_id.id:
|
|
income_account = line.product_id.property_account_income_id.id
|
|
elif line.product_id.categ_id.property_account_income_categ_id.id:
|
|
income_account = line.product_id.categ_id.property_account_income_categ_id.id
|
|
else:
|
|
raise UserError(_('Please define income ' \
|
|
'account for this product: "%s" (id:%d).') \
|
|
% (line.product_id.name, line.product_id.id))
|
|
|
|
name = line.product_id.name
|
|
if line.notice:
|
|
# add discount reason in move
|
|
name = name + ' (' + line.notice + ')'
|
|
|
|
# Create a move for the line for the order line
|
|
insert_data('product', {
|
|
'name': name,
|
|
'quantity': line.qty,
|
|
'product_id': line.product_id.id,
|
|
'account_id': income_account,
|
|
'analytic_account_id': self._prepare_analytic_account(cr, uid, line, context=context),
|
|
'credit': ((amount > 0) and amount) or 0.0,
|
|
'debit': ((amount < 0) and -amount) or 0.0,
|
|
'tax_ids': [(6, 0, line.tax_ids_after_fiscal_position.ids)],
|
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(
|
|
order.partner_id).id or False
|
|
})
|
|
|
|
# Create the tax lines
|
|
taxes = []
|
|
for t in line.tax_ids_after_fiscal_position:
|
|
if t.company_id.id == current_company.id:
|
|
taxes.append(t.id)
|
|
if not taxes:
|
|
continue
|
|
for tax in account_tax_obj.browse(cr, uid, taxes, context=context).compute_all(
|
|
line.price_unit * (100.0 - line.discount) / 100.0, cur, line.qty)['taxes']:
|
|
insert_data('tax', {
|
|
'name': _('Tax') + ' ' + tax['name'],
|
|
'product_id': line.product_id.id,
|
|
'quantity': line.qty,
|
|
'account_id': tax['account_id'] or income_account,
|
|
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
|
|
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
|
|
'tax_line_id': tax['id'],
|
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(
|
|
order.partner_id).id or False
|
|
})
|
|
|
|
# round tax lines per order
|
|
if rounding_method == 'round_globally':
|
|
for group_key, group_value in grouped_data.iteritems():
|
|
if group_key[0] == 'tax':
|
|
for line in group_value:
|
|
line['credit'] = cur.round(line['credit'])
|
|
line['debit'] = cur.round(line['debit'])
|
|
|
|
if order.discount_total:
|
|
insert_data('counter_part', {
|
|
'name': 'Discount',
|
|
'account_id': order.session_id.config_id.discount_account.id,
|
|
'credit': ((order.discount_total < 0) and -order.discount_total) or 0.0,
|
|
'debit': ((order.discount_total > 0) and order.discount_total) or 0.0,
|
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(
|
|
order.partner_id).id or False
|
|
})
|
|
elif order.discount_percent:
|
|
discount = (100 * order.amount_total) / (100 - order.discount_percent)
|
|
discount -= order.amount_total
|
|
print discount, "--------------"
|
|
insert_data('counter_part', {
|
|
'name': 'Discount',
|
|
'account_id': order.session_id.config_id.discount_account.id,
|
|
'credit': ((discount < 0) and -discount) or 0.0,
|
|
'debit': ((discount > 0) and discount) or 0.0,
|
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(
|
|
order.partner_id).id or False
|
|
})
|
|
# counterpart
|
|
insert_data('counter_part', {
|
|
'name': _("Trade Receivables"), # order.name,
|
|
'account_id': order_account,
|
|
'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
|
|
'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
|
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(
|
|
order.partner_id).id or False
|
|
})
|
|
|
|
order.write({'state': 'done', 'account_move': move_id})
|
|
|
|
all_lines = []
|
|
for group_key, group_data in grouped_data.iteritems():
|
|
for value in group_data:
|
|
all_lines.append((0, 0, value), )
|
|
if move_id: # In case no order was changed
|
|
self.pool.get("account.move").write(cr, SUPERUSER_ID, [move_id], {'line_ids': all_lines},
|
|
context=dict(context or {}, dont_create_taxes=True))
|
|
self.pool.get("account.move").post(cr, SUPERUSER_ID, [move_id], context=context)
|
|
|
|
return True
|
|
|
|
|
|
class NewPosLines(osv.osv):
|
|
_inherit = "pos.order.line"
|
|
|
|
_columns = {
|
|
'price_unit': fields.float(string='Unit Price', digits=0),
|
|
'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
|
|
'discount_fixed': fields.float('Discount Fixed'),
|
|
'discountStr': fields.char('discountStr'),
|
|
|
|
}
|
|
|
|
_defaults = {
|
|
'discount_fixed': lambda *a: 0.0,
|
|
}
|
|
|
|
@api.onchange('discount_fixed')
|
|
def change_discount_fixed_line(self):
|
|
if self.discount_fixed:
|
|
self.discount = 0.0
|
|
|
|
@api.onchange('discount')
|
|
def change_discount_line(self):
|
|
if self.discount:
|
|
self.discount_fixed = 0.0
|
|
|
|
@api.onchange('qty')
|
|
def change_qty_line(self):
|
|
self._compute_amount_line_all()
|
|
|
|
@api.onchange('price_unit')
|
|
def change_price_unit_line(self):
|
|
self._compute_amount_line_all()
|
|
|
|
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'discount_fixed', 'product_id')
|
|
def _compute_amount_line_all(self):
|
|
for line in self:
|
|
currency = line.order_id.pricelist_id.currency_id
|
|
taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.id)
|
|
fiscal_position_id = line.order_id.fiscal_position_id
|
|
if fiscal_position_id:
|
|
taxes = fiscal_position_id.map_tax(taxes)
|
|
# =============== finding subtotal for each orderline=========================================================
|
|
if line.discount_fixed != 0:
|
|
price = line.price_unit
|
|
line.price_subtotal = line.price_subtotal_incl = price * line.qty - line.discount_fixed
|
|
else:
|
|
price = line.price_unit
|
|
line.price_subtotal = line.price_subtotal_incl = (price * line.qty) - (price * line.qty * (line.discount or 0.0) / 100)
|
|
if taxes:
|
|
taxes = taxes.compute_all(price, currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
|
|
line.price_subtotal = taxes['total_excluded']
|
|
line.price_subtotal_incl = taxes['total_included']
|
|
line.price_subtotal = currency.round(line.price_subtotal)
|
|
line.price_subtotal_incl = currency.round(line.price_subtotal_incl)
|
|
|
|
|
|
class AccountMoveLineExtra(models.Model):
|
|
_inherit = 'account.move.line'
|
|
|
|
_sql_constraints = [
|
|
('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
|
|
('credit_debit2', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
|
|
]
|
|
|
|
@api.multi
|
|
def _update_check(self):
|
|
""" This module is overwritten, to avoid the warning raised when we edit the journal entries"""
|
|
move_ids = set()
|
|
for line in self:
|
|
err_msg = _('Move name (id): %s (%s)') % (line.move_id.name, str(line.move_id.id))
|
|
if line.move_id.id not in move_ids:
|
|
move_ids.add(line.move_id.id)
|
|
self.env['account.move'].browse(list(move_ids))._check_lock_date()
|
|
return True
|
|
|
|
@api.multi
|
|
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False):
|
|
""" This function is overwritten to remove some warnings"""
|
|
# Perform all checks on lines
|
|
company_ids = set()
|
|
all_accounts = []
|
|
partners = set()
|
|
for line in self:
|
|
company_ids.add(line.company_id.id)
|
|
all_accounts.append(line.account_id)
|
|
if (line.account_id.internal_type in ('receivable', 'payable')):
|
|
partners.add(line.partner_id.id)
|
|
if line.reconciled:
|
|
raise UserError(_('You are trying to reconcile some entries that are already reconciled!'))
|
|
if len(company_ids) > 1:
|
|
raise UserError(_('To reconcile the entries company should be the same for all entries!'))
|
|
if not all_accounts[0].reconcile:
|
|
raise UserError(_('The account %s (%s) is not marked as reconciliable !') % (
|
|
all_accounts[0].name, all_accounts[0].code))
|
|
# reconcile everything that can be
|
|
remaining_moves = self.auto_reconcile_lines()
|
|
|
|
# if writeoff_acc_id specified, then create write-off move with value the remaining amount from move in self
|
|
if writeoff_acc_id and writeoff_journal_id and remaining_moves:
|
|
all_aml_share_same_currency = all([x.currency_id == self[0].currency_id for x in self])
|
|
writeoff_vals = {
|
|
'account_id': writeoff_acc_id.id,
|
|
'journal_id': writeoff_journal_id.id
|
|
}
|
|
if not all_aml_share_same_currency:
|
|
writeoff_vals['amount_currency'] = False
|
|
writeoff_to_reconcile = remaining_moves._create_writeoff(writeoff_vals)
|
|
# add writeoff line to reconcile algo and finish the reconciliation
|
|
remaining_moves = (remaining_moves + writeoff_to_reconcile).auto_reconcile_lines()
|
|
return writeoff_to_reconcile
|
|
return True
|
|
|
|
|
|
class AccountMoveNew(models.Model):
|
|
_inherit = 'account.move'
|
|
|
|
@api.multi
|
|
def assert_balanced(self):
|
|
"""Overwritten to remove the warning raised.(For editing the journal entry)"""
|
|
if not self.ids:
|
|
return True
|
|
prec = self.env['decimal.precision'].precision_get('Account')
|
|
|
|
self._cr.execute("""\
|
|
SELECT move_id
|
|
FROM account_move_line
|
|
WHERE move_id in %s
|
|
GROUP BY move_id
|
|
HAVING abs(sum(debit) - sum(credit)) > %s
|
|
""", (tuple(self.ids), 10 ** (-max(5, prec))))
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|