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__':