From 0aefb2bd14509fab4dfd8a28d927c9c78c39fb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 1 Aug 2013 16:07:01 +0200 Subject: [PATCH] Split anglo_saxon_quantity for in and out usage The anglo_saxon_quantity must be different for direct move from supplier to customer. issue3300 review984002 --- stock.py | 59 ++- ...t_stock_anglo_saxon_with_drop_shipment.rst | 344 ++++++++++++++++++ tests/test_account_stock_anglo_saxon.py | 4 + 3 files changed, 396 insertions(+), 11 deletions(-) create mode 100644 tests/scenario_account_stock_anglo_saxon_with_drop_shipment.rst diff --git a/stock.py b/stock.py index ef7a979193..06dada0e2c 100644 --- a/stock.py +++ b/stock.py @@ -5,27 +5,60 @@ from trytond.model import fields from trytond.pyson import Eval from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction +from trytond.backend import TableHandler __all__ = ['Move'] __metaclass__ = PoolMeta +def _get_field(type_): + if type_.startswith('in_'): + return 'in_anglo_saxon_quantity' + else: + return 'out_anglo_saxon_quantity' + + class Move: __name__ = 'stock.move' - anglo_saxon_quantity = fields.Float('Anglo-Saxon Quantity', required=True, - digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) + in_anglo_saxon_quantity = fields.Float('Input Anglo-Saxon Quantity', + required=True, digits=(16, Eval('unit_digits', 2)), + depends=['unit_digits']) + out_anglo_saxon_quantity = fields.Float('Output Anglo-Saxon Quantity', + required=True, digits=(16, Eval('unit_digits', 2)), + depends=['unit_digits']) @classmethod def __setup__(cls): super(Move, cls).__setup__() cls._sql_constraints += [ - ('check_anglo_saxon_quantity', - 'CHECK(quantity >= anglo_saxon_quantity)', + ('check_in_anglo_saxon_quantity', + 'CHECK(quantity >= in_anglo_saxon_quantity)', + 'Anglo-Saxon quantity can not be greater than quantity.'), + ('check_out_anglo_saxon_quantity', + 'CHECK(quantity >= out_anglo_saxon_quantity)', 'Anglo-Saxon quantity can not be greater than quantity.'), ] + @classmethod + def __register__(cls, module_name): + cursor = Transaction().cursor + super(Move, cls).__register__(module_name) + table = TableHandler(cursor, cls, module_name) + + # Migration from 2.8: split anglo_saxon_quantity + if table.column_exist('anglo_saxon_quantity'): + cursor.execute('UPDATE "' + cls._table + '" ' + 'SET in_anglo_saxon_quantity = anglo_saxon_quantity, ' + 'out_anglo_saxon_quantity = anglo_saxon_quantity') + table.drop_constraint('check_anglo_saxon_quantity') + table.drop_column('anglo_saxon_quantity') + @staticmethod - def default_anglo_saxon_quantity(): + def default_in_anglo_saxon_quantity(): + return 0.0 + + @staticmethod + def default_out_anglo_saxon_quantity(): return 0.0 def _get_account_stock_move_lines(self, type_): @@ -80,10 +113,12 @@ class Move: Uom = pool.get('product.uom') Currency = pool.get('currency.currency') + as_qty_field = _get_field(type_) + consumed_qty = 0.0 for move in moves: qty = Uom.compute_qty(move.uom, - move.quantity - move.anglo_saxon_quantity, + move.quantity - getattr(move, as_qty_field), move.product.default_uom, round=False) if qty <= 0.0: continue @@ -121,6 +156,7 @@ class Move: total_qty = Uom.compute_qty(uom, quantity, product.default_uom, round=False) + as_qty_field = _get_field(type_) cost = Decimal('0.0') consumed_qty = 0.0 for move, move_qty, move_cost_price in cls._get_anglo_saxon_move( @@ -131,9 +167,9 @@ class Move: with Transaction().set_user(0, set_context=True): cls.write([move], { - 'anglo_saxon_quantity': ((move.anglo_saxon_quantity or 0.0) - + move_qty), - }) + as_qty_field: ( + (getattr(move, as_qty_field) or 0.0) + move_qty), + }) if consumed_qty < total_qty: qty = total_qty - consumed_qty @@ -146,6 +182,7 @@ class Move: if default is None: default = {} default = default.copy() - default.setdefault('anglo_saxon_quantity', - cls.default_anglo_saxon_quantity()) + for prefix in ('in_', 'out_'): + default.setdefault(prefix + 'anglo_saxon_quantity', + getattr(cls, 'default_%sanglo_saxon_quantity' % prefix)()) return super(Move, cls).copy(moves, default=default) diff --git a/tests/scenario_account_stock_anglo_saxon_with_drop_shipment.rst b/tests/scenario_account_stock_anglo_saxon_with_drop_shipment.rst new file mode 100644 index 0000000000..2c92b7d68b --- /dev/null +++ b/tests/scenario_account_stock_anglo_saxon_with_drop_shipment.rst @@ -0,0 +1,344 @@ +===================================================== +Account Stock Anglo-Saxon with Drop Shipment Scenario +===================================================== + +============= +General Setup +============= + +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() + +Install sale_supply, sale, purchase:: + + >>> Module = Model.get('ir.module.module') + >>> modules = Module.find([ + ... ('name', 'in', ('account_stock_anglo_saxon', + ... 'sale_supply_drop_shipment', 'sale', 'purchase')), + ... ]) + >>> Module.install([x.id for x in modules], config.context) + >>> Wizard('ir.module.module.install_upgrade').execute('upgrade') + +Create company:: + + >>> Currency = Model.get('currency.currency') + >>> CurrencyRate = Model.get('currency.currency.rate') + >>> Company = Model.get('company.company') + >>> Party = Model.get('party.party') + >>> company_config = Wizard('company.company.config') + >>> company_config.execute('company') + >>> company = company_config.form + >>> party = Party(name='Dunder Mifflin') + >>> party.save() + >>> company.party = party + >>> currencies = Currency.find([('code', '=', 'USD')]) + >>> if not currencies: + ... currency = Currency(name='Euro', symbol=u'$', code='USD', + ... 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.currency = currency + >>> company_config.execute('add') + >>> company, = Company.find() + +Reload the context:: + + >>> User = Model.get('res.user') + >>> Group = Model.get('res.group') + >>> config._context = User.get_preferences(True, config.context) + +Create sale user:: + + >>> sale_user = User() + >>> sale_user.name = 'Sale' + >>> sale_user.login = 'sale' + >>> sale_user.main_company = company + >>> sale_group, = Group.find([('name', '=', 'Sales')]) + >>> sale_user.groups.append(sale_group) + >>> sale_user.save() + +Create purchase user:: + + >>> purchase_user = User() + >>> purchase_user.name = 'Purchase' + >>> purchase_user.login = 'purchase' + >>> purchase_user.main_company = company + >>> purchase_group, = Group.find([('name', '=', 'Purchase')]) + >>> purchase_user.groups.append(purchase_group) + >>> purchase_request_group, = Group.find( + ... [('name', '=', 'Purchase Request')]) + >>> purchase_user.groups.append(purchase_request_group) + >>> purchase_user.save() + +Create stock user:: + + >>> stock_user = User() + >>> stock_user.name = 'Stock' + >>> stock_user.login = 'stock' + >>> stock_user.main_company = company + >>> stock_group, = Group.find([('name', '=', 'Stock')]) + >>> stock_user.groups.append(stock_group) + >>> stock_user.save() + +Create account user:: + + >>> account_user = User() + >>> account_user.name = 'Account' + >>> account_user.login = 'account' + >>> account_user.main_company = company + >>> account_group, = Group.find([('name', '=', 'Account')]) + >>> account_user.groups.append(account_group) + >>> account_user.save() + +Create fiscal year:: + + >>> FiscalYear = Model.get('account.fiscalyear') + >>> Sequence = Model.get('ir.sequence') + >>> SequenceStrict = Model.get('ir.sequence.strict') + >>> fiscalyear = FiscalYear(name='%s' % 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_sequence = Sequence(name='%s' % today.year, + ... code='account.move', + ... company=company) + >>> post_move_sequence.save() + >>> fiscalyear.post_move_sequence = post_move_sequence + >>> invoice_sequence = SequenceStrict(name='%s' % today.year, + ... code='account.invoice', + ... company=company) + >>> invoice_sequence.save() + >>> fiscalyear.out_invoice_sequence = invoice_sequence + >>> fiscalyear.in_invoice_sequence = invoice_sequence + >>> fiscalyear.out_credit_note_sequence = invoice_sequence + >>> fiscalyear.in_credit_note_sequence = invoice_sequence + >>> fiscalyear.save() + >>> FiscalYear.create_period([fiscalyear.id], config.context) + +Create chart of accounts:: + + >>> AccountTemplate = Model.get('account.account.template') + >>> Account = Model.get('account.account') + >>> AccountJournal = Model.get('account.journal') + >>> 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), + ... ]) + >>> (stock, stock_customer, stock_lost_found, stock_production, + ... stock_supplier) = Account.find([ + ... ('kind', '=', 'stock'), + ... ('company', '=', company.id), + ... ('name', 'like', 'Stock%'), + ... ], order=[('name', 'ASC')]) + >>> cogs, = Account.find([ + ... ('kind', '=', 'other'), + ... ('company', '=', company.id), + ... ('name', '=', 'COGS'), + ... ]) + >>> create_chart.form.account_receivable = receivable + >>> create_chart.form.account_payable = payable + >>> create_chart.execute('create_properties') + >>> stock_journal, = AccountJournal.find([('code', '=', 'STO')]) + +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') + >>> ProductSupplier = Model.get('purchase.product_supplier') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + >>> ProductTemplate = Model.get('product.template') + >>> Product = Model.get('product.product') + >>> product = Product() + >>> template = ProductTemplate() + >>> template.name = 'product' + >>> template.default_uom = unit + >>> template.type = 'goods' + >>> template.purchasable = True + >>> template.salable = True + >>> template.list_price = Decimal('10') + >>> template.cost_price = Decimal('5') + >>> template.cost_price_method = 'fixed' + >>> template.delivery_time = 0 + >>> template.account_expense = expense + >>> template.account_revenue = revenue + >>> template.account_stock = stock + >>> template.account_cogs = cogs + >>> template.account_stock_supplier = stock_supplier + >>> template.account_stock_customer = stock_customer + >>> template.account_stock_production = stock_production + >>> template.account_stock_lost_found = stock_lost_found + >>> template.account_journal_stock_supplier = stock_journal + >>> template.account_journal_stock_customer = stock_journal + >>> template.account_journal_stock_lost_found = stock_journal + >>> template.supply_on_sale = True + >>> template.save() + >>> product.template = template + >>> product.save() + >>> product_supplier = ProductSupplier() + >>> product_supplier.product = template + >>> product_supplier.party = supplier + >>> product_supplier.drop_shipment = True + >>> product_supplier.delivery_time = 0 + >>> product_supplier.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() + +Sale 50 products:: + + >>> config.user = sale_user.id + >>> Sale = Model.get('sale.sale') + >>> SaleLine = Model.get('sale.line') + >>> sale = Sale() + >>> sale.party = customer + >>> sale.payment_term = payment_term + >>> sale_line = SaleLine() + >>> sale.lines.append(sale_line) + >>> sale_line.product = product + >>> sale_line.quantity = 50 + >>> sale.save() + >>> Sale.quote([sale.id], config.context) + >>> Sale.confirm([sale.id], config.context) + >>> Sale.process([sale.id], config.context) + >>> sale.state + u'processing' + +Create Purchase from Request:: + + >>> config.user = purchase_user.id + >>> Purchase = Model.get('purchase.purchase') + >>> PurchaseRequest = Model.get('purchase.request') + >>> purchase_request, = PurchaseRequest.find() + >>> create_purchase = Wizard('purchase.request.create_purchase', + ... [purchase_request]) + >>> create_purchase.form.payment_term = payment_term + >>> create_purchase.execute('start') + >>> purchase, = Purchase.find() + >>> purchase_line, = purchase.lines + >>> purchase_line.unit_price = Decimal('3') + >>> purchase.save() + >>> Purchase.quote([purchase.id], config.context) + >>> Purchase.confirm([purchase.id], config.context) + >>> purchase.reload() + >>> purchase.state + u'confirmed' + >>> config.user = sale_user.id + >>> sale.reload() + >>> sale.shipments + [] + >>> shipment, = sale.drop_shipments + +Receive 50 products:: + + >>> config.user = stock_user.id + >>> ShipmentDrop = Model.get('stock.shipment.drop') + >>> ShipmentDrop.done([shipment.id], config.context) + >>> shipment.state + u'done' + >>> stock_supplier.reload() + >>> (stock_supplier.debit, stock_supplier.credit) == \ + ... (Decimal('0'), Decimal('150')) + True + >>> stock_customer.reload() + >>> (stock_customer.debit, stock_customer.credit) == \ + ... (Decimal('150'), Decimal('0')) + True + >>> stock.reload() + >>> (stock.debit, stock.credit) == \ + ... (Decimal('0'), Decimal('0')) + True + +Open supplier invoice:: + + >>> Invoice = Model.get('account.invoice') + >>> config.user = purchase_user.id + >>> purchase.reload() + >>> invoice, = purchase.invoices + >>> config.user = account_user.id + >>> invoice.invoice_date = today + >>> invoice.save() + >>> Invoice.post([invoice.id], config.context) + >>> invoice.state + u'posted' + >>> payable.reload() + >>> (payable.debit, payable.credit) == \ + ... (Decimal('0.00'), Decimal('150.00')) + True + >>> expense.reload() + >>> (expense.debit, expense.credit) == \ + ... (Decimal('150.00'), Decimal('150.00')) + True + >>> stock_supplier.reload() + >>> (stock_supplier.debit, stock_supplier.credit) == \ + ... (Decimal('150.00'), Decimal('150.00')) + True + +Open customer invoice:: + + >>> config.user = sale_user.id + >>> sale.reload() + >>> invoice, = sale.invoices + >>> config.user = account_user.id + >>> Invoice.post([invoice.id], config.context) + >>> invoice.state + u'posted' + >>> receivable.reload() + >>> (receivable.debit, receivable.credit) == \ + ... (Decimal('500.00'), Decimal('0.00')) + True + >>> revenue.reload() + >>> (revenue.debit, revenue.credit) == \ + ... (Decimal('0.00'), Decimal('500.00')) + True + >>> stock_customer.reload() + >>> (stock_customer.debit, stock_customer.credit) == \ + ... (Decimal('150.00'), Decimal('150.00')) + True + >>> cogs.reload() + >>> (cogs.debit, cogs.credit) == \ + ... (Decimal('150.00'), Decimal('0.00')) + True diff --git a/tests/test_account_stock_anglo_saxon.py b/tests/test_account_stock_anglo_saxon.py index ad5a7bf087..423cd3b556 100644 --- a/tests/test_account_stock_anglo_saxon.py +++ b/tests/test_account_stock_anglo_saxon.py @@ -58,6 +58,10 @@ def suite(): 'scenario_account_stock_anglo_saxon.rst', setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8', optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) + suite.addTests(doctest.DocFileSuite( + 'scenario_account_stock_anglo_saxon_with_drop_shipment.rst', + setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8', + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite if __name__ == '__main__':