Manage negative amounts in sale
review274001 issue1795
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Manage negative amounts in sale
|
||||
* Add cache on amount fields in readonly states
|
||||
* Confirmed state split into Confirmed and Processing
|
||||
* Add a Function field delivery_date to sale line
|
||||
|
||||
@@ -76,6 +76,7 @@ optional or completed with sensible default values:
|
||||
- Invoices: The list of related invoices.
|
||||
- Moves: The list of related stock moves.
|
||||
- Shipments: The list of related shipments.
|
||||
- Return Shipments: The list of related shipments return.
|
||||
|
||||
The *Sale* report allow to print the sale orders or to send
|
||||
them by mail.
|
||||
|
||||
@@ -148,7 +148,10 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
('exception', 'Exception'),
|
||||
], 'Shipment State', readonly=True, required=True)
|
||||
shipments = fields.Function(fields.One2Many('stock.shipment.out', None,
|
||||
'Shipments'), 'get_function_fields')
|
||||
'Shipments'), 'get_shipments')
|
||||
shipment_returns = fields.Function(
|
||||
fields.One2Many('stock.shipment.out.return', None, 'Shipment Returns'),
|
||||
'get_shipment_returns')
|
||||
moves = fields.Function(fields.One2Many('stock.move', None, 'Moves'),
|
||||
'get_function_fields')
|
||||
|
||||
@@ -444,8 +447,6 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
res['currency_digits'] = self.get_currency_digits(sales)
|
||||
if 'party_lang' in names:
|
||||
res['party_lang'] = self.get_party_lang(sales)
|
||||
if 'shipments' in names:
|
||||
res['shipments'] = self.get_shipments(sales)
|
||||
if 'moves' in names:
|
||||
res['moves'] = self.get_moves(sales)
|
||||
return res
|
||||
@@ -543,23 +544,23 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
'invoice_state': state,
|
||||
})
|
||||
|
||||
def get_shipments(self, sales):
|
||||
'''
|
||||
Return shipment_out ids for each sales
|
||||
def get_shipments_returns(attribute):
|
||||
"Computes the returns or shipments"
|
||||
def method(self, ids, name):
|
||||
shipments = {}
|
||||
for sale in self.browse(ids):
|
||||
shipments[sale.id] = []
|
||||
for line in sale.lines:
|
||||
for move in line.moves:
|
||||
ship_or_return = getattr(move, attribute)
|
||||
if bool(ship_or_return):
|
||||
if ship_or_return.id not in shipments[sale.id]:
|
||||
shipments[sale.id].append(ship_or_return.id)
|
||||
return shipments
|
||||
return method
|
||||
|
||||
:param sales: a BrowseRecordList of sales
|
||||
:return: a dictionary with sale id as key and
|
||||
a list of shipment_out id as value
|
||||
'''
|
||||
res = {}
|
||||
for sale in sales:
|
||||
res[sale.id] = []
|
||||
for line in sale.lines:
|
||||
for move in line.moves:
|
||||
if move.shipment_out:
|
||||
if move.shipment_out.id not in res[sale.id]:
|
||||
res[sale.id].append(move.shipment_out.id)
|
||||
return res
|
||||
get_shipments = get_shipments_returns('shipment_out')
|
||||
get_shipment_returns = get_shipments_returns('shipment_out_return')
|
||||
|
||||
def get_moves(self, sales):
|
||||
'''
|
||||
@@ -647,7 +648,11 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
if not sale.invoice_address or not sale.shipment_address:
|
||||
self.raise_user_error('addresses_required')
|
||||
for line in sale.lines:
|
||||
if (not line.from_location
|
||||
if line.quantity >= 0:
|
||||
location = line.from_location
|
||||
else:
|
||||
location = line.to_location
|
||||
if (not location
|
||||
and line.product
|
||||
and line.product.type in ('goods', 'assets')):
|
||||
self.raise_user_error('warehouse_required')
|
||||
@@ -685,31 +690,24 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
'total_amount_cache': sale.total_amount,
|
||||
})
|
||||
|
||||
def _get_invoice_line_sale_line(self, sale):
|
||||
def _get_invoice_line_sale_line(self, sale, invoice_type):
|
||||
'''
|
||||
Return invoice line values for each sale lines
|
||||
|
||||
:param sale: the BrowseRecord of the sale
|
||||
|
||||
:return: a dictionary with invoiced sale line id as key
|
||||
and a list of invoice lines values as value
|
||||
Return invoice line values for each sale lines according to
|
||||
invoice_type
|
||||
'''
|
||||
line_obj = Pool().get('sale.line')
|
||||
res = {}
|
||||
for line in sale.lines:
|
||||
if (invoice_type == 'out_invoice') != (line.quantity >= 0):
|
||||
continue
|
||||
val = line_obj.get_invoice_line(line)
|
||||
if val:
|
||||
res[line.id] = val
|
||||
return res
|
||||
|
||||
def _get_invoice_sale(self, sale):
|
||||
def _get_invoice_sale(self, sale, invoice_type):
|
||||
'''
|
||||
Return invoice values for sale
|
||||
|
||||
:param sale: the BrowseRecord of the sale
|
||||
|
||||
:return: a dictionary with invoice fields as key and
|
||||
invoice values as value
|
||||
Return invoice values of type invoice_type for sale
|
||||
'''
|
||||
journal_obj = Pool().get('account.journal')
|
||||
|
||||
@@ -721,7 +719,7 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
|
||||
res = {
|
||||
'company': sale.company.id,
|
||||
'type': 'out_invoice',
|
||||
'type': invoice_type,
|
||||
'reference': sale.reference,
|
||||
'journal': journal_id,
|
||||
'party': sale.party.id,
|
||||
@@ -732,9 +730,9 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
}
|
||||
return res
|
||||
|
||||
def create_invoice(self, sale):
|
||||
def create_invoice(self, sale, invoice_type):
|
||||
'''
|
||||
Create an invoice for the sale and return the id
|
||||
Create an invoice of type invoice_type for the sale and return the id
|
||||
'''
|
||||
pool = Pool()
|
||||
invoice_obj = pool.get('account.invoice')
|
||||
@@ -745,12 +743,12 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
self.raise_user_error('missing_account_receivable',
|
||||
error_args=(sale.party.rec_name,))
|
||||
|
||||
invoice_lines = self._get_invoice_line_sale_line(sale)
|
||||
invoice_lines = self._get_invoice_line_sale_line(sale, invoice_type)
|
||||
if not invoice_lines:
|
||||
return
|
||||
|
||||
|
||||
vals = self._get_invoice_sale(sale)
|
||||
vals = self._get_invoice_sale(sale, invoice_type)
|
||||
with Transaction().set_user(0, set_context=True):
|
||||
invoice_id = invoice_obj.create(vals)
|
||||
|
||||
@@ -773,19 +771,21 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
})
|
||||
return invoice_id
|
||||
|
||||
def _get_move_sale_line(self, sale):
|
||||
def _get_move_sale_line(self, sale, shipment_type):
|
||||
'''
|
||||
Return a dictionary of move values for each sale lines
|
||||
|
||||
:param sale: the BrowseRecord of the sale
|
||||
|
||||
:return: a dictionary with move as key and move values as value
|
||||
Return a dictionary of move values for each sale lines of the right
|
||||
shipment_type
|
||||
'''
|
||||
line_obj = Pool().get('sale.line')
|
||||
res = {}
|
||||
for line in sale.lines:
|
||||
val = line_obj.get_move(line)
|
||||
if val:
|
||||
if not val:
|
||||
continue
|
||||
out_move = (val['to_location']
|
||||
== line.sale.party.customer_location.id)
|
||||
out_shipment = shipment_type == 'out'
|
||||
if out_move == out_shipment:
|
||||
res[line.id] = val
|
||||
return res
|
||||
|
||||
@@ -808,20 +808,28 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
('warehouse', line.warehouse.id),
|
||||
)
|
||||
|
||||
def create_shipment(self, sale):
|
||||
_group_return_key = _group_shipment_key
|
||||
|
||||
def create_shipment(self, sale, shipment_type):
|
||||
'''
|
||||
Create a shipment for the sale
|
||||
Create a shipment of type shipment_type for the sale
|
||||
and return the list of created shipment ids
|
||||
'''
|
||||
pool = Pool()
|
||||
shipment_obj = pool.get('stock.shipment.out')
|
||||
move_obj = pool.get('stock.move')
|
||||
sale_line_obj = pool.get('sale.line')
|
||||
|
||||
moves = self._get_move_sale_line(sale)
|
||||
moves = self._get_move_sale_line(sale, shipment_type)
|
||||
if not moves:
|
||||
return
|
||||
keyfunc = partial(self._group_shipment_key, moves.values())
|
||||
if shipment_type == 'out':
|
||||
keyfunc = partial(self._group_shipment_key, moves.values())
|
||||
move_shipment_key = 'shipment_out'
|
||||
shipment_obj = pool.get('stock.shipment.out')
|
||||
elif shipment_type == 'return':
|
||||
keyfunc = partial(self._group_return_key, moves.values())
|
||||
move_shipment_key = 'shipment_out_return'
|
||||
shipment_obj = pool.get('stock.shipment.out.return')
|
||||
moves = moves.items()
|
||||
moves = sorted(moves, key=keyfunc)
|
||||
|
||||
@@ -838,12 +846,13 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
shipment_id = shipment_obj.create(values)
|
||||
shipments.append(shipment_id)
|
||||
for line_id, values in grouped_moves:
|
||||
values['shipment_out'] = shipment_id
|
||||
values[move_shipment_key] = shipment_id
|
||||
move_id = move_obj.create(values)
|
||||
sale_line_obj.write(line_id, {
|
||||
'moves': [('add', move_id)],
|
||||
})
|
||||
shipment_obj.wait(shipments)
|
||||
if shipment_type == 'out':
|
||||
shipment_obj.wait(shipments)
|
||||
return shipments
|
||||
|
||||
def is_done(self, sale):
|
||||
@@ -878,9 +887,11 @@ class Sale(Workflow, ModelSQL, ModelView):
|
||||
for sale in self.browse(ids):
|
||||
if sale.state in ('done', 'cancel'):
|
||||
continue
|
||||
self.create_invoice(sale)
|
||||
self.create_invoice(sale, 'out_invoice')
|
||||
self.create_invoice(sale, 'out_credit_note')
|
||||
self.set_invoice_state(sale)
|
||||
self.create_shipment(sale)
|
||||
self.create_shipment(sale, 'out')
|
||||
self.create_shipment(sale, 'return')
|
||||
self.set_shipment_state(sale)
|
||||
if self.is_done(sale):
|
||||
done.append(sale.id)
|
||||
@@ -1283,16 +1294,22 @@ class SaleLine(ModelSQL, ModelView):
|
||||
def get_from_location(self, ids, name):
|
||||
result = {}
|
||||
for line in self.browse(ids):
|
||||
if line.warehouse:
|
||||
if line.quantity >= 0 and line.warehouse:
|
||||
result[line.id] = line.warehouse.output_location.id
|
||||
else:
|
||||
result[line.id] = None
|
||||
result[line.id] = line.sale.party.customer_location.id
|
||||
return result
|
||||
|
||||
def get_to_location(self, ids, name):
|
||||
result = {}
|
||||
for line in self.browse(ids):
|
||||
result[line.id] = line.sale.party.customer_location.id
|
||||
if line.quantity >= 0:
|
||||
result[line.id] = line.sale.party.customer_location.id
|
||||
else:
|
||||
if line.warehouse:
|
||||
result[line.id] = line.warehouse.input_location.id
|
||||
else:
|
||||
result[line.id] = None
|
||||
return result
|
||||
|
||||
def _compute_delivery_date(self, product, date):
|
||||
@@ -1339,7 +1356,7 @@ class SaleLine(ModelSQL, ModelView):
|
||||
if (line.sale.invoice_method == 'order'
|
||||
or not line.product
|
||||
or line.product.type == 'service'):
|
||||
quantity = line.quantity
|
||||
quantity = abs(line.quantity)
|
||||
else:
|
||||
quantity = 0.0
|
||||
for move in line.moves:
|
||||
@@ -1406,7 +1423,7 @@ class SaleLine(ModelSQL, ModelView):
|
||||
if line.product.type == 'service':
|
||||
return
|
||||
skip_ids = set(x.id for x in line.moves_recreated)
|
||||
quantity = line.quantity
|
||||
quantity = abs(line.quantity)
|
||||
for move in line.moves:
|
||||
if move.id not in skip_ids:
|
||||
quantity -= uom_obj.compute_qty(move.uom, move.quantity,
|
||||
@@ -1542,17 +1559,19 @@ class Template(ModelSQL, ModelView):
|
||||
|
||||
def on_change_with_sale_uom(self, vals):
|
||||
uom_obj = Pool().get('product.uom')
|
||||
res = None
|
||||
|
||||
if vals.get('default_uom'):
|
||||
default_uom = uom_obj.browse(vals['default_uom'])
|
||||
if vals.get('sale_uom'):
|
||||
sale_uom = uom_obj.browse(vals['sale_uom'])
|
||||
if default_uom.category.id == sale_uom.category.id:
|
||||
return sale_uom.id
|
||||
res = sale_uom.id
|
||||
else:
|
||||
return default_uom.id
|
||||
res = default_uom.id
|
||||
else:
|
||||
return default_uom.id
|
||||
res = default_uom.id
|
||||
return res
|
||||
|
||||
Template()
|
||||
|
||||
@@ -1653,17 +1672,68 @@ class ShipmentOut(ModelSQL, ModelView):
|
||||
sale_obj.process(sale_ids)
|
||||
return res
|
||||
|
||||
def button_draft(self, ids):
|
||||
@ModelView.button
|
||||
@Workflow.transition('draft')
|
||||
def draft(self, ids):
|
||||
for shipment in self.browse(ids):
|
||||
for move in shipment.outgoing_moves:
|
||||
if move.state == 'cancel' and move.sale_line:
|
||||
self.raise_user_error('reset_move')
|
||||
|
||||
return super(ShipmentOut, self).button_draft(ids)
|
||||
return super(ShipmentOut, self).draft(ids)
|
||||
|
||||
ShipmentOut()
|
||||
|
||||
|
||||
class ShipmentOutReturn(ModelSQL, ModelView):
|
||||
_name = 'stock.shipment.out.return'
|
||||
|
||||
def __init__(self):
|
||||
super(ShipmentOutReturn, self).__init__()
|
||||
self._error_messages.update({
|
||||
'reset_move': 'You cannot reset to draft a move generated '
|
||||
'by a sale.',
|
||||
})
|
||||
|
||||
def write(self, ids, vals):
|
||||
sale_obj = Pool().get('sale.sale')
|
||||
sale_line_obj = Pool().get('sale.line')
|
||||
|
||||
res = super(ShipmentOutReturn, self).write(ids, vals)
|
||||
|
||||
if 'state' in vals and vals['state'] == 'received':
|
||||
sale_ids = []
|
||||
move_ids = []
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
for shipment in self.browse(ids):
|
||||
move_ids.extend([x.id for x in shipment.incoming_moves])
|
||||
|
||||
sale_line_ids = sale_line_obj.search([
|
||||
('moves', 'in', move_ids),
|
||||
])
|
||||
if sale_line_ids:
|
||||
for sale_line in sale_line_obj.browse(sale_line_ids):
|
||||
if sale_line.sale.id not in sale_ids:
|
||||
sale_ids.append(sale_line.sale.id)
|
||||
|
||||
with Transaction().set_user(0, set_context=True):
|
||||
sale_obj.process(sale_ids)
|
||||
return res
|
||||
|
||||
@ModelView.button
|
||||
@Workflow.transition('draft')
|
||||
def draft(self, ids):
|
||||
for shipment in self.browse(ids):
|
||||
for move in shipment.incoming_moves:
|
||||
if move.state == 'cancel' and move.sale_line:
|
||||
self.raise_user_error('reset_move')
|
||||
|
||||
return super(ShipmentOutReturn, self).draft(ids)
|
||||
|
||||
ShipmentOutReturn()
|
||||
|
||||
|
||||
class Move(ModelSQL, ModelView):
|
||||
_name = 'stock.move'
|
||||
|
||||
@@ -1996,3 +2066,47 @@ class HandleInvoiceException(Wizard):
|
||||
return 'end'
|
||||
|
||||
HandleInvoiceException()
|
||||
|
||||
|
||||
class ReturnSaleAsk(ModelView):
|
||||
_name = 'sale.return_sale.ask'
|
||||
sale_lines = fields.Many2Many('sale.line', None, None, 'Return Lines',
|
||||
domain=[('sale', '=', Eval('sale'))], depends=['sale'])
|
||||
sale = fields.Many2One('sale.sale', 'Sale')
|
||||
|
||||
ReturnSaleAsk()
|
||||
|
||||
|
||||
class ReturnSale(Wizard):
|
||||
_name = 'sale.return_sale'
|
||||
start_state = 'ask'
|
||||
ask = StateView('sale.return_sale.ask', 'sale.return_sale_ask_form', [
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('Ok', 'make_return', 'tryton-ok', default=True),
|
||||
])
|
||||
make_return = StateTransition()
|
||||
|
||||
def default_ask(self, session, fields):
|
||||
return {
|
||||
'sale': Transaction().context['active_id'],
|
||||
}
|
||||
|
||||
def transition_make_return(self, session):
|
||||
pool = Pool()
|
||||
sale_obj = pool.get('sale.sale')
|
||||
line_obj = pool.get('sale.line')
|
||||
|
||||
sale = sale_obj.browse(session.ask.sale.id)
|
||||
new_sale_type = 'return' if sale.type == 'sale' else 'sale'
|
||||
new_sale_id = sale_obj.copy(session.ask.sale.id, {
|
||||
'type': new_sale_type,
|
||||
})
|
||||
new_line_ids = line_obj.copy([x.id for x in session.ask.sale_lines])
|
||||
for new_line in line_obj.browse(new_line_ids):
|
||||
line_obj.write(new_line.id, {'quantity': -new_line.quantity})
|
||||
sale_obj.write(new_sale_id, {
|
||||
'lines': [('set', new_line_ids)],
|
||||
})
|
||||
return 'end'
|
||||
|
||||
ReturnSale()
|
||||
|
||||
@@ -130,6 +130,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="moves" colspan="4"
|
||||
view_ids="sale.move_view_list_shipment"/>
|
||||
<field name="shipments" colspan="4"/>
|
||||
<field name="shipment_returns" colspan="4"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<field name="currency_digits" invisible="1" colspan="6"/>
|
||||
@@ -174,7 +175,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
id="choose"
|
||||
yalign="0.0" xalign="0.0" xexpand="1"/>
|
||||
<field name="recreate_moves" colspan="2"
|
||||
view_ids="stock.move_view_tree_simple"/>
|
||||
view_ids="stock.move_view_tree"/>
|
||||
</form>
|
||||
]]>
|
||||
</field>
|
||||
@@ -208,6 +209,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="model">sale.sale,-1</field>
|
||||
<field name="action" ref="act_shipment_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_return_form">
|
||||
<field name="name">Returns</field>
|
||||
<field name="res_model">stock.shipment.out.return</field>
|
||||
<field name="domain">[("id", "in", Eval('shipment_returns'))]</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_open_shipment_return_keyword1">
|
||||
<field name="keyword">form_relate</field>
|
||||
<field name="model">sale.sale,-1</field>
|
||||
<field name="action" ref="act_return_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_invoice_form">
|
||||
<field name="name">Invoices</field>
|
||||
<field name="res_model">account.invoice</field>
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
=============
|
||||
Sale Scenario
|
||||
=============
|
||||
|
||||
Imports::
|
||||
|
||||
>>> import datetime
|
||||
>>> from dateutil.relativedelta import relativedelta
|
||||
>>> from decimal import Decimal
|
||||
>>> from proteus import config, Model, Wizard
|
||||
>>> today = datetime.date.today()
|
||||
|
||||
Create database::
|
||||
|
||||
>>> config = config.set_trytond()
|
||||
>>> config.pool.test = True
|
||||
|
||||
Install sale::
|
||||
|
||||
>>> Module = Model.get('ir.module.module')
|
||||
>>> sale_module, = Module.find([('name', '=', 'sale')])
|
||||
>>> Module.install([sale_module.id], config.context)
|
||||
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
|
||||
|
||||
Create company::
|
||||
|
||||
>>> Currency = Model.get('currency.currency')
|
||||
>>> CurrencyRate = Model.get('currency.currency.rate')
|
||||
>>> currencies = Currency.find([('code', '=', 'EUR')])
|
||||
>>> if not currencies:
|
||||
... currency = Currency(name='Euro', symbol=u'€', code='EUR',
|
||||
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
|
||||
... mon_decimal_point=',')
|
||||
... currency.save()
|
||||
... CurrencyRate(date=today + relativedelta(month=1, day=1),
|
||||
... rate=Decimal('1.0'), currency=currency).save()
|
||||
... else:
|
||||
... currency, = currencies
|
||||
>>> Company = Model.get('company.company')
|
||||
>>> company_config = Wizard('company.company.config')
|
||||
>>> company_config.execute('company')
|
||||
>>> company = company_config.form
|
||||
>>> company.name = 'B2CK'
|
||||
>>> company.currency = currency
|
||||
>>> company_config.execute('add')
|
||||
>>> company, = Company.find([])
|
||||
|
||||
Reload the context::
|
||||
|
||||
>>> User = Model.get('res.user')
|
||||
>>> config._context = User.get_preferences(True, config.context)
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> FiscalYear = Model.get('account.fiscalyear')
|
||||
>>> Sequence = Model.get('ir.sequence')
|
||||
>>> SequenceStrict = Model.get('ir.sequence.strict')
|
||||
>>> fiscalyear = FiscalYear(name=str(today.year))
|
||||
>>> fiscalyear.start_date = today + relativedelta(month=1, day=1)
|
||||
>>> fiscalyear.end_date = today + relativedelta(month=12, day=31)
|
||||
>>> fiscalyear.company = company
|
||||
>>> post_move_seq = Sequence(name=str(today.year), code='account.move',
|
||||
... company=company)
|
||||
>>> post_move_seq.save()
|
||||
>>> fiscalyear.post_move_sequence = post_move_seq
|
||||
>>> invoice_seq = SequenceStrict(name=str(today.year),
|
||||
... code='account.invoice', company=company)
|
||||
>>> invoice_seq.save()
|
||||
>>> fiscalyear.out_invoice_sequence = invoice_seq
|
||||
>>> fiscalyear.in_invoice_sequence = invoice_seq
|
||||
>>> fiscalyear.out_credit_note_sequence = invoice_seq
|
||||
>>> fiscalyear.in_credit_note_sequence = invoice_seq
|
||||
>>> fiscalyear.save()
|
||||
>>> FiscalYear.create_period([fiscalyear.id], config.context)
|
||||
True
|
||||
|
||||
Create chart of accounts::
|
||||
|
||||
>>> AccountTemplate = Model.get('account.account.template')
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> account_template, = AccountTemplate.find([('parent', '=', False)])
|
||||
>>> create_chart = Wizard('account.create_chart')
|
||||
>>> create_chart.execute('account')
|
||||
>>> create_chart.form.account_template = account_template
|
||||
>>> create_chart.form.company = company
|
||||
>>> create_chart.execute('create_account')
|
||||
>>> receivable, = Account.find([
|
||||
... ('kind', '=', 'receivable'),
|
||||
... ('company', '=', company.id),
|
||||
... ])
|
||||
>>> payable, = Account.find([
|
||||
... ('kind', '=', 'payable'),
|
||||
... ('company', '=', company.id),
|
||||
... ])
|
||||
>>> revenue, = Account.find([
|
||||
... ('kind', '=', 'revenue'),
|
||||
... ('company', '=', company.id),
|
||||
... ])
|
||||
>>> expense, = Account.find([
|
||||
... ('kind', '=', 'expense'),
|
||||
... ('company', '=', company.id),
|
||||
... ])
|
||||
>>> create_chart.form.account_receivable = receivable
|
||||
>>> create_chart.form.account_payable = payable
|
||||
>>> create_chart.execute('create_properties')
|
||||
|
||||
Create parties::
|
||||
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> supplier = Party(name='Supplier')
|
||||
>>> supplier.save()
|
||||
>>> customer = Party(name='Customer')
|
||||
>>> customer.save()
|
||||
|
||||
Create category::
|
||||
|
||||
>>> ProductCategory = Model.get('product.category')
|
||||
>>> category = ProductCategory(name='Category')
|
||||
>>> category.save()
|
||||
|
||||
Create product::
|
||||
|
||||
>>> ProductUom = Model.get('product.uom')
|
||||
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
|
||||
>>> Product = Model.get('product.product')
|
||||
>>> product = Product()
|
||||
>>> product.name = 'product'
|
||||
>>> product.category = category
|
||||
>>> product.default_uom = unit
|
||||
>>> product.type = 'goods'
|
||||
>>> product.purchasable = True
|
||||
>>> product.salable = True
|
||||
>>> product.list_price = Decimal('10')
|
||||
>>> product.cost_price = Decimal('5')
|
||||
>>> product.cost_price_method = 'fixed'
|
||||
>>> product.account_expense = expense
|
||||
>>> product.account_revenue = revenue
|
||||
>>> product.save()
|
||||
|
||||
Create payment term::
|
||||
|
||||
>>> PaymentTerm = Model.get('account.invoice.payment_term')
|
||||
>>> PaymentTermLine = Model.get('account.invoice.payment_term.line')
|
||||
>>> payment_term = PaymentTerm(name='Direct')
|
||||
>>> payment_term_line = PaymentTermLine(type='remainder', days=0)
|
||||
>>> payment_term.lines.append(payment_term_line)
|
||||
>>> payment_term.save()
|
||||
|
||||
Create an Inventory::
|
||||
|
||||
>>> Inventory = Model.get('stock.inventory')
|
||||
>>> InventoryLine = Model.get('stock.inventory.line')
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> storage, = Location.find([
|
||||
... ('code', '=', 'STO'),
|
||||
... ])
|
||||
>>> inventory = Inventory()
|
||||
>>> inventory.location = storage
|
||||
>>> inventory.save()
|
||||
>>> inventory_line = InventoryLine(product=product, inventory=inventory)
|
||||
>>> inventory_line.quantity = 100.0
|
||||
>>> inventory_line.expected_quantity = 0.0
|
||||
>>> inventory.save()
|
||||
>>> inventory_line.save()
|
||||
>>> Inventory.confirm([inventory.id], config.context)
|
||||
>>> inventory.state
|
||||
u'done'
|
||||
|
||||
Sale 5 products::
|
||||
|
||||
>>> Sale = Model.get('sale.sale')
|
||||
>>> SaleLine = Model.get('sale.line')
|
||||
>>> sale = Sale()
|
||||
>>> sale.party = customer
|
||||
>>> sale.payment_term = payment_term
|
||||
>>> sale.invoice_method = 'shipment'
|
||||
>>> sale_line = SaleLine()
|
||||
>>> sale.lines.append(sale_line)
|
||||
>>> sale_line.product = product
|
||||
>>> sale_line.quantity = 2.0
|
||||
>>> sale_line = SaleLine()
|
||||
>>> sale.lines.append(sale_line)
|
||||
>>> sale_line.product = product
|
||||
>>> sale_line.quantity = 3.0
|
||||
>>> sale.save()
|
||||
>>> Sale.quote([sale.id], config.context)
|
||||
>>> Sale.confirm([sale.id], config.context)
|
||||
>>> Sale.process([sale.id], config.context)
|
||||
>>> sale.state
|
||||
u'processing'
|
||||
|
||||
Validate Shipments::
|
||||
|
||||
>>> sale.reload()
|
||||
>>> ShipmentOut = Model.get('stock.shipment.out')
|
||||
>>> shipment, = sale.shipments
|
||||
>>> ShipmentOut.assign_try([shipment.id], config.context)
|
||||
True
|
||||
>>> ShipmentOut.pack([shipment.id], config.context)
|
||||
>>> ShipmentOut.done([shipment.id], config.context)
|
||||
|
||||
Open customer invoice::
|
||||
|
||||
>>> sale.reload()
|
||||
>>> Invoice = Model.get('account.invoice')
|
||||
>>> invoice, = sale.invoices
|
||||
>>> invoice.type
|
||||
u'out_invoice'
|
||||
>>> Invoice.open([invoice.id], config.context)
|
||||
>>> invoice.reload()
|
||||
>>> invoice.state
|
||||
u'open'
|
||||
>>> receivable.reload()
|
||||
>>> (receivable.debit, receivable.credit) == \
|
||||
... (Decimal('50.00'), Decimal('0.00'))
|
||||
True
|
||||
>>> revenue.reload()
|
||||
>>> (revenue.debit, revenue.credit) == \
|
||||
... (Decimal('0.00'), Decimal('50.00'))
|
||||
True
|
||||
|
||||
Create a Return::
|
||||
|
||||
>>> return_ = Sale()
|
||||
>>> return_.party = customer
|
||||
>>> return_.payment_term = payment_term
|
||||
>>> return_.invoice_method = 'shipment'
|
||||
>>> return_line = SaleLine()
|
||||
>>> return_.lines.append(return_line)
|
||||
>>> return_line.product = product
|
||||
>>> return_line.quantity = -4.
|
||||
>>> return_.save()
|
||||
>>> Sale.quote([return_.id], config.context)
|
||||
>>> Sale.confirm([return_.id], config.context)
|
||||
>>> Sale.process([return_.id], config.context)
|
||||
>>> return_.state
|
||||
u'processing'
|
||||
|
||||
Check Return Shipments::
|
||||
|
||||
>>> return_.reload()
|
||||
>>> ShipmentReturn = Model.get('stock.shipment.out.return')
|
||||
>>> ship_return, = return_.shipment_returns
|
||||
>>> len(return_.shipments)
|
||||
0
|
||||
>>> ShipmentReturn.receive([ship_return.id], config.context)
|
||||
>>> move_return, = ship_return.incoming_moves
|
||||
>>> move_return.product.name
|
||||
u'product'
|
||||
>>> move_return.quantity
|
||||
4.0
|
||||
|
||||
Open customer credit note::
|
||||
|
||||
>>> return_.reload()
|
||||
>>> credit_note, = return_.invoices
|
||||
>>> credit_note.type
|
||||
u'out_credit_note'
|
||||
>>> Invoice.open([credit_note.id], config.context)
|
||||
>>> credit_note.reload()
|
||||
>>> credit_note.state
|
||||
u'open'
|
||||
>>> receivable.reload()
|
||||
>>> (receivable.debit, receivable.credit) == (Decimal(50), Decimal(40))
|
||||
True
|
||||
>>> revenue.reload()
|
||||
>>> (revenue.debit, revenue.credit) == (Decimal(40), Decimal(50))
|
||||
True
|
||||
|
||||
Mixing return and sale::
|
||||
|
||||
>>> mix = Sale()
|
||||
>>> mix.party = customer
|
||||
>>> mix.payment_term = payment_term
|
||||
>>> mix.invoice_method = 'order'
|
||||
>>> mixline = SaleLine()
|
||||
>>> mix.lines.append(mixline)
|
||||
>>> mixline.product = product
|
||||
>>> mixline.quantity = 7.
|
||||
>>> mixline2 = SaleLine()
|
||||
>>> mix.lines.append(mixline2)
|
||||
>>> mixline2.product = product
|
||||
>>> mixline2.quantity = -2.
|
||||
>>> mix.save()
|
||||
>>> Sale.quote([mix.id], config.context)
|
||||
>>> Sale.confirm([mix.id], config.context)
|
||||
>>> Sale.process([mix.id], config.context)
|
||||
>>> mix.state
|
||||
u'processing'
|
||||
|
||||
Checking Shipments::
|
||||
|
||||
>>> mix.reload()
|
||||
>>> mix_returns, = mix.shipment_returns
|
||||
>>> mix_shipments, = mix.shipments
|
||||
>>> ShipmentReturn.receive([mix_returns.id], config.context)
|
||||
>>> move_return, = mix_returns.incoming_moves
|
||||
>>> move_return.product.name
|
||||
u'product'
|
||||
>>> move_return.quantity
|
||||
2.0
|
||||
>>> ShipmentOut.assign_try([mix_shipments.id], config.context)
|
||||
True
|
||||
>>> ShipmentOut.pack([mix_shipments.id], config.context)
|
||||
>>> ShipmentOut.done([mix_shipments.id], config.context)
|
||||
>>> move_shipment, = mix_shipments.outgoing_moves
|
||||
>>> move_shipment.product.name
|
||||
u'product'
|
||||
>>> move_shipment.quantity
|
||||
7.0
|
||||
|
||||
Checking the invoice::
|
||||
|
||||
>>> mix.reload()
|
||||
>>> mix_invoice, mix_credit_note = mix.invoices
|
||||
>>> mix_invoice.type, mix_credit_note.type
|
||||
(u'out_invoice', u'out_credit_note')
|
||||
>>> Invoice.open([mix_invoice.id], config.context)
|
||||
>>> mix_invoice.reload()
|
||||
>>> mix_invoice.state
|
||||
u'open'
|
||||
>>> Invoice.open([mix_credit_note.id], config.context)
|
||||
>>> mix_credit_note.reload()
|
||||
>>> mix_credit_note.state
|
||||
u'open'
|
||||
>>> receivable.reload()
|
||||
>>> (receivable.debit, receivable.credit) == (Decimal(120), Decimal(60))
|
||||
True
|
||||
>>> revenue.reload()
|
||||
>>> (revenue.debit, revenue.credit) == (Decimal(60), Decimal(120))
|
||||
True
|
||||
|
||||
Mixing stuff with an invoice method 'on shipment'::
|
||||
|
||||
>>> mix = Sale()
|
||||
>>> mix.party = customer
|
||||
>>> mix.payment_term = payment_term
|
||||
>>> mix.invoice_method = 'shipment'
|
||||
>>> mixline = SaleLine()
|
||||
>>> mix.lines.append(mixline)
|
||||
>>> mixline.product = product
|
||||
>>> mixline.quantity = 6.
|
||||
>>> mixline2 = SaleLine()
|
||||
>>> mix.lines.append(mixline2)
|
||||
>>> mixline2.product = product
|
||||
>>> mixline2.quantity = -3.
|
||||
>>> mix.save()
|
||||
>>> Sale.quote([mix.id], config.context)
|
||||
>>> Sale.confirm([mix.id], config.context)
|
||||
>>> Sale.process([mix.id], config.context)
|
||||
>>> mix.state
|
||||
u'processing'
|
||||
|
||||
Checking Shipments::
|
||||
|
||||
>>> mix.reload()
|
||||
>>> mix_returns, = mix.shipment_returns
|
||||
>>> mix_shipments, = mix.shipments
|
||||
>>> ShipmentReturn.receive([mix_returns.id], config.context)
|
||||
>>> move_return, = mix_returns.incoming_moves
|
||||
>>> move_return.product.name
|
||||
u'product'
|
||||
>>> move_return.quantity
|
||||
3.0
|
||||
>>> ShipmentOut.assign_try([mix_shipments.id], config.context)
|
||||
True
|
||||
>>> ShipmentOut.pack([mix_shipments.id], config.context)
|
||||
>>> move_shipment, = mix_shipments.outgoing_moves
|
||||
>>> move_shipment.product.name
|
||||
u'product'
|
||||
>>> move_shipment.quantity
|
||||
6.0
|
||||
@@ -9,8 +9,10 @@ if os.path.isdir(DIR):
|
||||
sys.path.insert(0, os.path.dirname(DIR))
|
||||
|
||||
import unittest
|
||||
import doctest
|
||||
import trytond.tests.test_tryton
|
||||
from trytond.tests.test_tryton import test_view, test_depends
|
||||
from trytond.backend.sqlite.database import Database as SQLiteDatabase
|
||||
|
||||
|
||||
class SaleTestCase(unittest.TestCase):
|
||||
@@ -33,9 +35,23 @@ class SaleTestCase(unittest.TestCase):
|
||||
'''
|
||||
test_depends()
|
||||
|
||||
|
||||
def doctest_dropdb(test):
|
||||
database = SQLiteDatabase().connect()
|
||||
cursor = database.cursor(autocommit=True)
|
||||
try:
|
||||
database.drop(cursor, ':memory:')
|
||||
cursor.commit()
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
|
||||
def suite():
|
||||
suite = trytond.tests.test_tryton.suite()
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(SaleTestCase))
|
||||
suite.addTests(doctest.DocFileSuite('scenario_sale.rst',
|
||||
setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
|
||||
return suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user