diff --git a/CHANGELOG b/CHANGELOG
index 0b8e7f2b90..30ab91c320 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
+* Manage negative amount in purchase
+
Version 2.4.0 - 2012-04-24
* Bug fixes (see mercurial logs for details)
* Add cache on amount fields in readonly states
diff --git a/__init__.py b/__init__.py
index 83be11b211..2001f593b0 100644
--- a/__init__.py
+++ b/__init__.py
@@ -24,6 +24,7 @@ def register():
ProductSupplier,
ProductSupplierPrice,
ShipmentIn,
+ ShipmentInReturn,
Move,
HandleShipmentExceptionAsk,
HandleInvoiceExceptionAsk,
diff --git a/doc/index.rst b/doc/index.rst
index 99512af6f9..f2da3d71cf 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -65,6 +65,7 @@ of them are 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 the related shipment returns.
The *Purchase* report allow to print the purchase orders or to send
them by mail.
diff --git a/purchase.py b/purchase.py
index d7086d042e..7671c11363 100644
--- a/purchase.py
+++ b/purchase.py
@@ -16,9 +16,10 @@ __all__ = ['Purchase', 'PurchaseInvoice', 'PurchaseIgnoredInvoice',
'PurchaseRecreadtedInvoice', 'PurchaseLine', 'PurchaseLineTax',
'PurchaseLineInvoiceLine', 'PurchaseLineIgnoredMove',
'PurchaseLineRecreatedMove', 'PurchaseReport', 'Template', 'Product',
- 'ProductSupplier', 'ProductSupplierPrice', 'ShipmentIn', 'Move',
- 'OpenSupplier', 'HandleShipmentExceptionAsk', 'HandleShipmentException',
- 'HandleInvoiceExceptionAsk', 'HandleInvoiceException']
+ 'ProductSupplier', 'ProductSupplierPrice', 'ShipmentIn',
+ 'ShipmentInReturn', 'Move', 'OpenSupplier', 'HandleShipmentExceptionAsk',
+ 'HandleShipmentException', 'HandleInvoiceExceptionAsk',
+ 'HandleInvoiceException']
__metaclass__ = PoolMeta
_STATES = {
@@ -128,6 +129,9 @@ class Purchase(Workflow, ModelSQL, ModelView):
], 'Shipment State', readonly=True, required=True)
shipments = fields.Function(fields.One2Many('stock.shipment.in', None,
'Shipments'), 'get_shipments')
+ shipment_returns = fields.Function(
+ fields.One2Many('stock.shipment.in.return', None, 'Shipment Returns'),
+ 'get_shipment_returns')
moves = fields.Function(fields.One2Many('stock.move', None, 'Moves'),
'get_moves')
@@ -452,14 +456,21 @@ class Purchase(Workflow, ModelSQL, ModelView):
'invoice_state': state,
})
- def get_shipments(self, name):
- shipments = []
- for line in self.lines:
- for move in line.moves:
- if move.shipment_in:
- if move.shipment_in.id not in shipments:
- shipments.append(move.shipment_in.id)
- return shipments
+ def get_shipments_returns(attribute):
+ "Computes the returns or shipments"
+ def method(self, name):
+ shipments = []
+ for line in self.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:
+ shipments.append(ship_or_return.id)
+ return shipments
+ return method
+
+ get_shipments = get_shipments_returns('shipment_in')
+ get_shipment_returns = get_shipments_returns('shipment_in_return')
def get_moves(self, name):
return [m.id for l in self.lines for m in l.moves]
@@ -562,20 +573,20 @@ class Purchase(Workflow, ModelSQL, ModelView):
'total_amount_cache': purchase.total_amount,
})
- def _get_invoice_line_purchase_line(self):
+ def _get_invoice_line_purchase_line(self, invoice_type):
'''
Return invoice line for each purchase lines
'''
res = {}
for line in self.lines:
- val = line.get_invoice_line()
+ val = line.get_invoice_line(invoice_type)
if val:
res[line.id] = val
return res
- def _get_invoice_purchase(self):
+ def _get_invoice_purchase(self, invoice_type):
'''
- Return invoice
+ Return invoice of type invoice_type
'''
pool = Pool()
Journal = pool.get('account.journal')
@@ -592,7 +603,7 @@ class Purchase(Workflow, ModelSQL, ModelView):
with Transaction().set_user(0, set_context=True):
return Invoice(
company=self.company,
- type='in_invoice',
+ type=invoice_type,
reference=self.reference,
journal=journal,
party=self.party,
@@ -602,7 +613,7 @@ class Purchase(Workflow, ModelSQL, ModelView):
payment_term=self.payment_term,
)
- def create_invoice(self):
+ def create_invoice(self, invoice_type):
'''
Create an invoice for the purchase and return it
'''
@@ -617,11 +628,11 @@ class Purchase(Workflow, ModelSQL, ModelView):
self.raise_user_error('missing_account_payable',
error_args=(self.party.rec_name,))
- invoice_lines = self._get_invoice_line_purchase_line()
+ invoice_lines = self._get_invoice_line_purchase_line(invoice_type)
if not invoice_lines:
return
- invoice = self._get_invoice_purchase()
+ invoice = self._get_invoice_purchase(invoice_type)
invoice.save()
for line in self.lines:
@@ -642,12 +653,40 @@ class Purchase(Workflow, ModelSQL, ModelView):
})
return invoice
- def create_move(self):
+ def create_move(self, move_type):
'''
Create move for each purchase lines
'''
+ new_moves = []
for line in self.lines:
- line.create_move()
+ if (line.quantity >= 0) != (move_type == 'in'):
+ continue
+ move = line.create_move()
+ if move:
+ new_moves.append(move)
+ return new_moves
+
+ def _get_return_shipment(self):
+ ShipmentInReturn = Pool().get('stock.shipment.in.return')
+ with Transaction().set_user(0, set_context=True):
+ return ShipmentInReturn(
+ company=self.company,
+ reference=self.reference,
+ from_location=self.warehouse.storage_location,
+ to_location=self.party.supplier_location,
+ )
+
+ def create_return_shipment(self, return_moves):
+ '''
+ Create return shipment and return the shipment id
+ '''
+ ShipmentInReturn = Pool().get('stock.shipment.in.return')
+ return_shipment = self._get_return_shipment()
+ return_shipment.moves = return_moves
+ return_shipment.save()
+ with Transaction().set_user(0, set_context=True):
+ ShipmentInReturn.wait([return_shipment])
+ return return_shipment
def is_done(self):
return ((self.invoice_state == 'paid'
@@ -707,9 +746,13 @@ class Purchase(Workflow, ModelSQL, ModelView):
for purchase in purchases:
if purchase.state in ('done', 'cancel'):
continue
- purchase.create_invoice()
+ purchase.create_invoice('in_invoice')
+ purchase.create_invoice('in_credit_note')
purchase.set_invoice_state()
- purchase.create_move()
+ purchase.create_move('in')
+ return_moves = purchase.create_move('return')
+ if return_moves:
+ purchase.create_return_shipment(return_moves)
purchase.set_shipment_state()
if purchase.is_done():
done.append(purchase)
@@ -1039,11 +1082,17 @@ class PurchaseLine(ModelSQL, ModelView):
return Decimal('0.0')
def get_from_location(self, name):
- return self.purchase.party.supplier_location.id
+ if self.quantity >= 0:
+ return self.purchase.party.supplier_location.id
+ elif self.purchase.warehouse:
+ return self.purchase.warehouse.storage_location.id
def get_to_location(self, name):
- if self.purchase.warehouse:
- return self.purchase.warehouse.input_location.id
+ if self.quantity >= 0:
+ if self.purchase.warehouse:
+ return self.purchase.warehouse.input_location.id
+ else:
+ return self.purchase.party.supplier_location.id
def on_change_with_delivery_date(self, name=None):
if (self.product
@@ -1054,7 +1103,7 @@ class PurchaseLine(ModelSQL, ModelView):
if product_supplier.party == self.purchase.party:
return product_supplier.compute_supply_date(date=date)
- def get_invoice_line(self):
+ def get_invoice_line(self, invoice_type):
'''
Return a list of invoice line for purchase line
'''
@@ -1070,17 +1119,22 @@ class PurchaseLine(ModelSQL, ModelView):
invoice_line.note = self.note
if self.type != 'line':
if (self.purchase.invoice_method == 'order'
- and (all(l.quantity >= 0 for l in self.sale.lines
- if l.type == 'line')
- or all(l.quantity <= 0 for l in self.sale.lines
- if l.type == 'line'))):
+ and ((all(l.quantity >= 0 for l in self.purchase.lines
+ if l.type == 'line')
+ and invoice_type == 'in_invoice')
+ or (all(l.quantity <= 0 for l in self.purchase.lines
+ if l.type == 'line')
+ and invoice_type == 'in_credit_note'))):
return [invoice_line]
else:
return []
+ if (invoice_type == 'in_invoice') != (self.quantity >= 0):
+ return []
+
if (self.purchase.invoice_method == 'order'
or not self.product
or self.product.type == 'service'):
- quantity = self.quantity
+ quantity = abs(self.quantity)
else:
quantity = 0.0
for move in self.moves:
@@ -1144,7 +1198,7 @@ class PurchaseLine(ModelSQL, ModelView):
if self.product.type == 'service':
return
skip = set(self.moves_recreated)
- quantity = self.quantity
+ quantity = abs(self.quantity)
for move in self.moves:
if move not in skip:
quantity -= Uom.compute_qty(move.uom, move.quantity,
@@ -1546,6 +1600,54 @@ class ShipmentIn:
return super(ShipmentIn, cls).draft(shipments)
+class ShipmentInReturn:
+ __name__ = 'stock.shipment.in.return'
+
+ @classmethod
+ def __setup__(cls):
+ super(ShipmentInReturn, cls).__setup__()
+ cls._error_messages.update({
+ 'reset_move': 'You cannot reset to draft a move generated '\
+ 'by a purchase.',
+ })
+
+ @classmethod
+ def write(cls, shipments, vals):
+ pool = Pool()
+ Purchase = pool.get('purchase.purchase')
+ PurchaseLine = pool.get('purchase.line')
+
+ super(ShipmentInReturn, cls).write(shipments, vals)
+
+ if 'state' in vals and vals['state'] == 'done':
+ move_ids = []
+ for shipment in shipments:
+ move_ids.extend([x.id for x in shipment.moves])
+
+ purchase_lines = PurchaseLine.search([
+ ('moves', 'in', move_ids),
+ ])
+ purchases = set()
+ if purchase_lines:
+ for purchase_line in purchase_lines:
+ purchases.add(purchase_line.purchase)
+
+ with Transaction().set_user(0, set_context=True):
+ purchases = Purchase.browse([p.id for p in purchases])
+ Purchase.process(purchases)
+
+ @classmethod
+ @ModelView.button
+ @Workflow.transition('draft')
+ def draft(cls, shipments):
+ for shipment in shipments:
+ for move in shipment.moves:
+ if move.state == 'cancel' and move.purchase_line:
+ cls.raise_user_error('reset_move')
+
+ return super(ShipmentInReturn, cls).draft(shipments)
+
+
class Move(ModelSQL, ModelView):
__name__ = 'stock.move'
purchase_line = fields.Many2One('purchase.line', 'Purchase Line',
diff --git a/purchase.xml b/purchase.xml
index 692233d2b7..bffd186907 100644
--- a/purchase.xml
+++ b/purchase.xml
@@ -130,6 +130,7 @@ this repository contains the full copyright notices and license terms. -->
+
@@ -174,6 +175,17 @@ this repository contains the full copyright notices and license terms. -->
purchase.purchase,-1
+
+ Returns
+ stock.shipment.in.return
+ [("id", "in", Eval('shipment_returns'))]
+
+
+ form_relate
+ purchase.purchase,-1
+
+
Invoices
account.invoice
@@ -199,7 +211,7 @@ this repository contains the full copyright notices and license terms. -->
id="choose"
yalign="0.0" xalign="0.0" xexpand="1"/>
+ view_ids="stock.move_view_tree"/>
]]>
@@ -733,5 +745,14 @@ this repository contains the full copyright notices and license terms. -->
+
+
+
+
+
+
+
+
+
diff --git a/tests/scenario_purchase.rst b/tests/scenario_purchase.rst
new file mode 100644
index 0000000000..99165af390
--- /dev/null
+++ b/tests/scenario_purchase.rst
@@ -0,0 +1,450 @@
+=================
+Purchase Scenario
+=================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> from decimal import Decimal
+ >>> from operator import attrgetter
+ >>> from proteus import config, Model, Wizard
+ >>> today = datetime.date.today()
+
+Create database::
+
+ >>> config = config.set_trytond()
+ >>> config.pool.test = True
+
+Install purchase::
+
+ >>> Module = Model.get('ir.module.module')
+ >>> purchase_module, = Module.find([('name', '=', 'purchase')])
+ >>> Module.install([purchase_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)
+
+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 product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+ >>> Product = Model.get('product.product')
+ >>> product = Product()
+ >>> product.name = 'product'
+ >>> 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'
+
+Purchase 5 products::
+
+ >>> Purchase = Model.get('purchase.purchase')
+ >>> PurchaseLine = Model.get('purchase.line')
+ >>> purchase = Purchase()
+ >>> purchase.party = supplier
+ >>> purchase.payment_term = payment_term
+ >>> purchase.invoice_method = 'order'
+ >>> purchase_line = PurchaseLine()
+ >>> purchase.lines.append(purchase_line)
+ >>> purchase_line.product = product
+ >>> purchase_line.quantity = 2.0
+ >>> purchase_line = PurchaseLine()
+ >>> purchase.lines.append(purchase_line)
+ >>> purchase_line.type = 'comment'
+ >>> purchase_line.description = 'Comment'
+ >>> purchase_line = PurchaseLine()
+ >>> purchase.lines.append(purchase_line)
+ >>> purchase_line.product = product
+ >>> purchase_line.quantity = 3.0
+ >>> purchase.save()
+ >>> Purchase.quote([purchase.id], config.context)
+ >>> Purchase.confirm([purchase.id], config.context)
+ >>> purchase.state
+ u'confirmed'
+ >>> purchase.reload()
+ >>> len(purchase.moves), len(purchase.shipment_returns), len(purchase.invoices)
+ (2, 0, 1)
+
+Purchase 5 products with an invoice method 'on shipment'::
+
+ >>> purchase = Purchase()
+ >>> purchase.party = supplier
+ >>> purchase.payment_term = payment_term
+ >>> purchase.invoice_method = 'shipment'
+ >>> purchase_line = PurchaseLine()
+ >>> purchase.lines.append(purchase_line)
+ >>> purchase_line.product = product
+ >>> purchase_line.quantity = 2.0
+ >>> purchase_line = PurchaseLine()
+ >>> purchase.lines.append(purchase_line)
+ >>> purchase_line.type = 'comment'
+ >>> purchase_line.description = 'Comment'
+ >>> purchase_line = PurchaseLine()
+ >>> purchase.lines.append(purchase_line)
+ >>> purchase_line.product = product
+ >>> purchase_line.quantity = 3.0
+ >>> purchase.save()
+ >>> Purchase.quote([purchase.id], config.context)
+ >>> Purchase.confirm([purchase.id], config.context)
+ >>> purchase.state
+ u'confirmed'
+ >>> purchase.reload()
+ >>> len(purchase.moves), len(purchase.shipment_returns), len(purchase.invoices)
+ (2, 0, 0)
+
+Validate Shipments::
+
+ >>> Move = Model.get('stock.move')
+ >>> ShipmentIn = Model.get('stock.shipment.in')
+ >>> shipment = ShipmentIn()
+ >>> shipment.supplier = supplier
+ >>> for move in purchase.moves:
+ ... incoming_move = Move(id=move.id)
+ ... shipment.incoming_moves.append(incoming_move)
+ >>> shipment.save()
+ >>> ShipmentIn.receive([shipment.id], config.context)
+ >>> ShipmentIn.done([shipment.id], config.context)
+ >>> purchase.reload()
+ >>> len(purchase.shipments), len(purchase.shipment_returns)
+ (1, 0)
+
+Open supplier invoice::
+
+ >>> purchase.reload()
+ >>> Invoice = Model.get('account.invoice')
+ >>> invoice, = purchase.invoices
+ >>> invoice.type
+ u'in_invoice'
+ >>> len(invoice.lines)
+ 2
+ >>> Invoice.open([invoice.id], config.context)
+ >>> invoice.reload()
+ >>> invoice.state
+ u'open'
+ >>> payable.reload()
+ >>> (payable.debit, payable.credit) == \
+ ... (Decimal('0.00'), Decimal('25.00'))
+ True
+ >>> expense.reload()
+ >>> (expense.debit, expense.credit) == \
+ ... (Decimal('25.00'), Decimal('0.00'))
+ True
+
+Create a Return::
+
+ >>> return_ = Purchase()
+ >>> return_.party = supplier
+ >>> return_.payment_term = payment_term
+ >>> return_.invoice_method = 'shipment'
+ >>> return_line = PurchaseLine()
+ >>> return_.lines.append(return_line)
+ >>> return_line.product = product
+ >>> return_line.quantity = -4.
+ >>> return_line = PurchaseLine()
+ >>> return_.lines.append(return_line)
+ >>> return_line.type = 'comment'
+ >>> return_line.description = 'Comment'
+ >>> return_.save()
+ >>> Purchase.quote([return_.id], config.context)
+ >>> Purchase.confirm([return_.id], config.context)
+ >>> return_.state
+ u'confirmed'
+ >>> return_.reload()
+ >>> (len(return_.shipments), len(return_.shipment_returns),
+ ... len(return_.invoices))
+ (0, 1, 0)
+
+Check Return Shipments::
+
+ >>> return_.reload()
+ >>> ShipmentReturn = Model.get('stock.shipment.in.return')
+ >>> ship_return, = return_.shipment_returns
+ >>> ship_return.state
+ u'waiting'
+ >>> move_return, = ship_return.moves
+ >>> move_return.product.name
+ u'product'
+ >>> move_return.quantity
+ 4.0
+ >>> ShipmentReturn.assign_try([ship_return.id], config.context)
+ True
+ >>> ShipmentReturn.done([ship_return.id], config.context)
+ >>> ship_return.reload()
+ >>> ship_return.state
+ u'done'
+
+Open supplier credit note::
+
+ >>> return_.reload()
+ >>> credit_note, = return_.invoices
+ >>> credit_note.type
+ u'in_credit_note'
+ >>> len(credit_note.lines)
+ 1
+ >>> Invoice.open([credit_note.id], config.context)
+ >>> credit_note.reload()
+ >>> credit_note.state
+ u'open'
+ >>> payable.reload()
+ >>> (payable.debit, payable.credit) == (Decimal(20), Decimal(25))
+ True
+ >>> expense.reload()
+ >>> (expense.debit, expense.credit) == (Decimal(25), Decimal(20))
+ True
+
+Mixing return and purchase::
+
+ >>> mix = Purchase()
+ >>> mix.party = supplier
+ >>> mix.payment_term = payment_term
+ >>> mix.invoice_method = 'order'
+ >>> mixline = PurchaseLine()
+ >>> mix.lines.append(mixline)
+ >>> mixline.product = product
+ >>> mixline.quantity = 7.
+ >>> mixline_comment = PurchaseLine()
+ >>> mix.lines.append(mixline_comment)
+ >>> mixline_comment.type = 'comment'
+ >>> mixline_comment.description = 'Comment'
+ >>> mixline2 = PurchaseLine()
+ >>> mix.lines.append(mixline2)
+ >>> mixline2.product = product
+ >>> mixline2.quantity = -2.
+ >>> mix.save()
+ >>> Purchase.quote([mix.id], config.context)
+ >>> Purchase.confirm([mix.id], config.context)
+ >>> mix.state
+ u'confirmed'
+ >>> mix.reload()
+ >>> len(mix.moves), len(mix.shipment_returns), len(mix.invoices)
+ (2, 1, 2)
+
+Checking Shipments::
+
+ >>> mix.reload()
+ >>> mix_returns, = mix.shipment_returns
+ >>> mix_shipments = ShipmentIn()
+ >>> mix_shipments.supplier = supplier
+ >>> for move in mix.moves:
+ ... if move.id in [m.id for m in mix_returns.moves]:
+ ... continue
+ ... incoming_move = Move(id=move.id)
+ ... mix_shipments.incoming_moves.append(incoming_move)
+ >>> mix_shipments.save()
+ >>> ShipmentIn.receive([mix_shipments.id], config.context)
+ >>> ShipmentIn.done([mix_shipments.id], config.context)
+ >>> mix.reload()
+ >>> len(mix.shipments)
+ 1
+
+ >>> ShipmentReturn.wait([mix_returns.id], config.context)
+ >>> ShipmentReturn.assign_try([mix_returns.id], config.context)
+ True
+ >>> ShipmentReturn.done([mix_returns.id], config.context)
+ >>> move_return, = mix_returns.moves
+ >>> move_return.product.name
+ u'product'
+ >>> move_return.quantity
+ 2.0
+
+Checking the invoice::
+
+ >>> mix.reload()
+ >>> mix_invoice, mix_credit_note = sorted(mix.invoices,
+ ... key=attrgetter('type'), reverse=True)
+ >>> mix_invoice.type, mix_credit_note.type
+ (u'in_invoice', u'in_credit_note')
+ >>> len(mix_invoice.lines), len(mix_credit_note.lines)
+ (1, 1)
+ >>> 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'
+ >>> payable.reload()
+ >>> (payable.debit, payable.credit) == (Decimal(30), Decimal(60))
+ True
+ >>> expense.reload()
+ >>> (expense.debit, expense.credit) == (Decimal(60), Decimal(30))
+ True
+
+Mixing stuff with an invoice method 'on shipment'::
+
+ >>> mix = Purchase()
+ >>> mix.party = supplier
+ >>> mix.payment_term = payment_term
+ >>> mix.invoice_method = 'shipment'
+ >>> mixline = PurchaseLine()
+ >>> mix.lines.append(mixline)
+ >>> mixline.product = product
+ >>> mixline.quantity = 6.
+ >>> mixline_comment = PurchaseLine()
+ >>> mix.lines.append(mixline_comment)
+ >>> mixline_comment.type = 'comment'
+ >>> mixline_comment.description = 'Comment'
+ >>> mixline2 = PurchaseLine()
+ >>> mix.lines.append(mixline2)
+ >>> mixline2.product = product
+ >>> mixline2.quantity = -3.
+ >>> mix.save()
+ >>> Purchase.quote([mix.id], config.context)
+ >>> Purchase.confirm([mix.id], config.context)
+ >>> mix.state
+ u'confirmed'
+ >>> mix.reload()
+ >>> len(mix.moves), len(mix.shipment_returns), len(mix.invoices)
+ (2, 1, 0)
+
+Checking Shipments::
+
+ >>> mix.reload()
+ >>> mix_returns, = mix.shipment_returns
+ >>> mix_shipments = ShipmentIn()
+ >>> mix_shipments.supplier = supplier
+ >>> for move in mix.moves:
+ ... if move.id in [m.id for m in mix_returns.moves]:
+ ... continue
+ ... incoming_move = Move(id=move.id)
+ ... mix_shipments.incoming_moves.append(incoming_move)
+ >>> mix_shipments.save()
+ >>> ShipmentIn.receive([mix_shipments.id], config.context)
+ >>> ShipmentIn.done([mix_shipments.id], config.context)
+ >>> mix.reload()
+ >>> len(mix.shipments)
+ 1
+
+ >>> ShipmentReturn.wait([mix_returns.id], config.context)
+ >>> ShipmentReturn.assign_try([mix_returns.id], config.context)
+ True
+ >>> ShipmentReturn.done([mix_returns.id], config.context)
+ >>> move_return, = mix_returns.moves
+ >>> move_return.product.name
+ u'product'
+ >>> move_return.quantity
+ 3.0
diff --git a/tests/test_purchase.py b/tests/test_purchase.py
index 4ed2b7bdf8..a672a91e70 100644
--- a/tests/test_purchase.py
+++ b/tests/test_purchase.py
@@ -10,8 +10,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 PurchaseTestCase(unittest.TestCase):
@@ -35,10 +37,23 @@ class PurchaseTestCase(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(
PurchaseTestCase))
+ suite.addTests(doctest.DocFileSuite('scenario_purchase.rst',
+ setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='UTF-8',
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite
if __name__ == '__main__':