Allow to sale and purchase goods indicating second uom

This commit is contained in:
Guillem Barba 2015-06-17 12:50:01 +02:00
parent 2dd1d89734
commit 99d9c4575b
15 changed files with 1100 additions and 0 deletions

View File

@ -1,3 +1,4 @@
* Allow to sale and purchase goods indicating second uom (extra depends)
* Adapt to latest patched version of stock module
Version 3.4.0 - 2015-06-08

View File

@ -3,6 +3,8 @@
from trytond.pool import Pool
from .product import *
from .stock import *
from .sale import *
from .purchase import *
def register():
@ -18,4 +20,9 @@ def register():
PeriodCacheLot,
Inventory,
InventoryLine,
SaleLine,
PurchaseLine,
module='stock_second_uom', type_='model')
Pool.register(
ReturnSale,
module='stock_second_uom', type_='wizard')

View File

@ -51,6 +51,46 @@ msgctxt "field:product.template,use_second_uom:"
msgid "Use Second UOM"
msgstr "Utilitza segona UdM"
msgctxt "field:purchase.line,product_second_uom_category:"
msgid "Product Second UoM Category"
msgstr "Categoria segona UdM del producte"
msgctxt "field:purchase.line,second_quantity:"
msgid "Second Quantity"
msgstr "Segona quantitat"
msgctxt "field:purchase.line,second_unit:"
msgid "Second Unit"
msgstr "Segona unitat"
msgctxt "field:purchase.line,second_unit_digits:"
msgid "Second Unit Digits"
msgstr "Dígits segona UdM"
msgctxt "field:purchase.line,use_second_unit:"
msgid "Use Second Unit"
msgstr "Utilitza segona unitat"
msgctxt "field:sale.line,product_second_uom_category:"
msgid "Product Second UoM Category"
msgstr "Categoria segona UdM del producte"
msgctxt "field:sale.line,second_quantity:"
msgid "Second Quantity"
msgstr "Segona quantitat"
msgctxt "field:sale.line,second_unit:"
msgid "Second Unit"
msgstr "Segona unitat"
msgctxt "field:sale.line,second_unit_digits:"
msgid "Second Unit Digits"
msgstr "Dígits segona UdM"
msgctxt "field:sale.line,use_second_unit:"
msgid "Use Second Unit"
msgstr "Utilitza segona unitat"
msgctxt "field:stock.inventory.line,second_expected_quantity:"
msgid "Second UoM Expected Quantity"
msgstr "Quantitat esperada segona UdM"

View File

@ -52,6 +52,46 @@ msgctxt "field:product.template,use_second_uom:"
msgid "Use Second UOM"
msgstr "Usa segunda UdM"
msgctxt "field:purchase.line,product_second_uom_category:"
msgid "Product Second UoM Category"
msgstr "Categoria segunda UdM del producto"
msgctxt "field:purchase.line,second_quantity:"
msgid "Second Quantity"
msgstr "Segunda cantidad"
msgctxt "field:purchase.line,second_unit:"
msgid "Second Unit"
msgstr "Segunda unidad"
msgctxt "field:purchase.line,second_unit_digits:"
msgid "Second Unit Digits"
msgstr "Dígitos segunda UdM"
msgctxt "field:purchase.line,use_second_unit:"
msgid "Use Second Unit"
msgstr "Usa segunda unidad"
msgctxt "field:sale.line,product_second_uom_category:"
msgid "Product Second UoM Category"
msgstr "Categoria segunda UdM del producto"
msgctxt "field:sale.line,second_quantity:"
msgid "Second Quantity"
msgstr "Segunda cantidad"
msgctxt "field:sale.line,second_unit:"
msgid "Second Unit"
msgstr "Segunda unidad"
msgctxt "field:sale.line,second_unit_digits:"
msgid "Second Unit Digits"
msgstr "Dígitos segunda UdM"
msgctxt "field:sale.line,use_second_unit:"
msgid "Use Second Unit"
msgstr "Usa segunda unidad"
msgctxt "field:stock.inventory.line,second_expected_quantity:"
msgid "Second UoM Expected Quantity"
msgstr "Cantidad esperada segunda UdM"

85
purchase.py Normal file
View File

@ -0,0 +1,85 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
__all__ = ['PurchaseLine']
__metaclass__ = PoolMeta
STATES = {
'invisible': (Eval('type') != 'line') | ~Eval('use_second_unit', False),
'required': (Eval('type') == 'line') & Eval('use_second_unit', False),
'readonly': ~Eval('_parent_sale', {}),
}
DEPENDS = ['type', 'use_second_unit']
class PurchaseLine:
__name__ = 'purchase.line'
use_second_unit = fields.Function(fields.Boolean('Use Second Unit'),
'on_change_with_use_second_unit')
second_quantity = fields.Float("Second Quantity",
digits=(16, Eval('second_unit_digits', 2)),
states=STATES, depends=DEPENDS + ['second_unit_digits'])
second_unit = fields.Many2One("product.uom", "Second Unit", domain=[
('category', '=', Eval('product_second_uom_category')),
],
states=STATES, depends=DEPENDS + ['product_second_uom_category'])
second_unit_digits = fields.Function(fields.Integer('Second Unit Digits'),
'on_change_with_second_unit_digits')
product_second_uom_category = fields.Function(
fields.Many2One('product.uom.category', 'Product Second UoM Category'),
'on_change_with_product_second_uom_category')
@fields.depends('product')
def on_change_with_use_second_unit(self, name=None):
if self.product and self.product.use_second_uom:
return True
return False
@fields.depends('second_unit')
def on_change_with_second_unit_digits(self, name=None):
if self.second_unit:
return self.second_unit.digits
return 2
@fields.depends('product')
def on_change_with_product_second_uom_category(self, name=None):
if self.product and self.product.use_second_uom:
return self.product.second_uom.category.id
@fields.depends('product', 'second_unit')
def on_change_product(self):
res = super(PurchaseLine, self).on_change_product()
if self.product and self.product.use_second_uom:
if (not self.second_unit
or self.second_unit.category
!= self.product.second_uom.category):
self.second_unit = self.product.second_uom
res['second_unit'] = self.product.second_uom.id
res['second_unit.rec_name'] = self.product.second_uom.rec_name
res['second_unit_digits'] = self.product.second_uom.digits
return res
def get_move(self):
pool = Pool()
Uom = pool.get('product.uom')
move = super(PurchaseLine, self).get_move()
if move and self.use_second_unit:
skip = set(self.moves_recreated)
second_quantity = abs(self.second_quantity)
for move in self.moves:
if move not in skip:
second_quantity -= Uom.compute_qty(
move.second_uom,
move.second_quantity,
self.second_unit)
second_quantity = max(
Uom.round(second_quantity, self.second_unit.rounding), 0)
move.second_quantity = second_quantity
move.second_uom = self.second_unit
return move

24
purchase.xml Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data depends="purchase">
<record model="ir.ui.view" id="purchase_line_view_form">
<field name="model">purchase.line</field>
<field name="inherit" ref="purchase.purchase_line_view_form"/>
<field name="name">purchase_line_form</field>
</record>
<record model="ir.ui.view" id="purchase_line_view_tree">
<field name="model">purchase.line</field>
<field name="inherit" ref="purchase.purchase_line_view_tree"/>
<field name="name">purchase_line_tree</field>
</record>
<record model="ir.ui.view" id="purchase_line_view_tree_sequence">
<field name="model">purchase.line</field>
<field name="inherit" ref="purchase.purchase_line_view_tree_sequence"/>
<field name="name">purchase_line_tree</field>
</record>
</data>
</tryton>

102
sale.py Normal file
View File

@ -0,0 +1,102 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
__all__ = ['SaleLine', 'ReturnSale']
__metaclass__ = PoolMeta
STATES = {
'invisible': (Eval('type') != 'line') | ~Eval('use_second_unit', False),
'required': (Eval('type') == 'line') & Eval('use_second_unit', False),
'readonly': ~Eval('_parent_sale', {}),
}
DEPENDS = ['type', 'use_second_unit']
class SaleLine:
__name__ = 'sale.line'
use_second_unit = fields.Function(fields.Boolean('Use Second Unit'),
'on_change_with_use_second_unit')
second_quantity = fields.Float("Second Quantity",
digits=(16, Eval('second_unit_digits', 2)),
states=STATES, depends=DEPENDS + ['second_unit_digits'])
second_unit = fields.Many2One("product.uom", "Second Unit", domain=[
('category', '=', Eval('product_second_uom_category')),
],
states=STATES, depends=DEPENDS + ['product_second_uom_category'])
second_unit_digits = fields.Function(fields.Integer('Second Unit Digits'),
'on_change_with_second_unit_digits')
product_second_uom_category = fields.Function(
fields.Many2One('product.uom.category', 'Product Second UoM Category'),
'on_change_with_product_second_uom_category')
@fields.depends('product')
def on_change_with_use_second_unit(self, name=None):
if self.product and self.product.use_second_uom:
return True
return False
@fields.depends('second_unit')
def on_change_with_second_unit_digits(self, name=None):
if self.second_unit:
return self.second_unit.digits
return 2
@fields.depends('product')
def on_change_with_product_second_uom_category(self, name=None):
if self.product and self.product.use_second_uom:
return self.product.second_uom.category.id
@fields.depends('product', 'second_unit')
def on_change_product(self):
res = super(SaleLine, self).on_change_product()
if self.product and self.product.use_second_uom:
if (not self.second_unit
or self.second_unit.category
!= self.product.second_uom.category):
self.second_unit = self.product.second_uom
res['second_unit'] = self.product.second_uom.id
res['second_unit.rec_name'] = self.product.second_uom.rec_name
res['second_unit_digits'] = self.product.second_uom.digits
return res
def get_move(self, shipment_type):
pool = Pool()
Uom = pool.get('product.uom')
move = super(SaleLine, self).get_move(shipment_type)
if move and self.use_second_unit:
skip = set(self.moves_recreated)
second_quantity = abs(self.second_quantity)
for move in self.moves:
if move not in skip:
second_quantity -= Uom.compute_qty(
move.second_uom,
move.second_quantity,
self.second_unit)
second_quantity = max(
Uom.round(second_quantity, self.second_unit.rounding), 0)
move.second_quantity = second_quantity
move.second_uom = self.second_unit
return move
class ReturnSale:
__name__ = 'sale.return_sale'
def do_return_(self, action):
Sale = Pool().get('sale.sale')
action, data = super(ReturnSale, self).do_return_(action)
return_sale_ids = data.get('res_id', [])
if return_sale_ids:
for sale in Sale.browse(return_sale_ids):
for line in sale.lines:
if line.second_quantity:
line.second_quantity *= -1
line.save()
return action, data

24
sale.xml Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data depends="sale">
<record model="ir.ui.view" id="sale_line_view_form">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_form"/>
<field name="name">sale_line_form</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_tree"/>
<field name="name">sale_line_tree</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_tree_sequence"/>
<field name="name">sale_line_tree</field>
</record>
</data>
</tryton>

View File

@ -0,0 +1,725 @@
=======================================
Second UoM Scenario with extras depends
=======================================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> today = datetime.date.today()
>>> last_month = today - relativedelta(months=1)
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install stock_second_uom Module::
>>> Module = Model.get('ir.module.module')
>>> stock_module, = Module.find([('name', '=', 'stock_second_uom')])
>>> stock_module.click('install')
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Install product_raw_variant Module::
>>> Module = Model.get('ir.module.module')
>>> stock_module, = Module.find([('name', '=', 'product_raw_variant')])
>>> stock_module.click('install')
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Install stock_lot Module::
>>> Module = Model.get('ir.module.module')
>>> stock_module, = Module.find([('name', '=', 'stock_lot')])
>>> stock_module.click('install')
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Install sale Module::
>>> Module = Model.get('ir.module.module')
>>> stock_module, = Module.find([('name', '=', 'sale')])
>>> stock_module.click('install')
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Install purchase Module::
>>> Module = Model.get('ir.module.module')
>>> stock_module, = Module.find([('name', '=', 'purchase')])
>>> stock_module.click('install')
>>> 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='US Dollar', symbol='$', 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')
>>> 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')
>>> Journal = Model.get('account.journal')
>>> account_template, = AccountTemplate.find([('parent', '=', None)])
>>> 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')
>>> cash, = Account.find([
... ('kind', '=', 'other'),
... ('name', '=', 'Main Cash'),
... ('company', '=', company.id),
... ])
>>> cash_journal, = Journal.find([('type', '=', 'cash')])
>>> cash_journal.credit_account = cash
>>> cash_journal.debit_account = cash
>>> cash_journal.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()
Get stock locations::
>>> Location = Model.get('stock.location')
>>> supplier_loc, = Location.find([('code', '=', 'SUP')])
>>> storage_loc, = Location.find([('code', '=', 'STO')])
>>> customer_loc, = Location.find([('code', '=', 'CUS')])
Create parties::
>>> Party = Model.get('party.party')
>>> supplier = Party(name='Supplier')
>>> supplier.save()
>>> customer = Party(name='Customer')
>>> customer.save()
Create products::
>>> ProductUom = Model.get('product.uom')
>>> ProductTemplate = Model.get('product.template')
>>> kg, = ProductUom.find([('name', '=', 'Kilogram')])
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'Product'
>>> template.default_uom = kg
>>> template.second_uom = unit
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.salable = True
>>> template.list_price = Decimal('300')
>>> template.cost_price = Decimal('80')
>>> template.cost_price_method = 'average'
>>> template.account_expense = expense
>>> template.account_revenue = revenue
>>> template.save()
>>> product_wo_2uom, = template.products
>>> product_w_2uom = template.products.new()
>>> product_w_2uom.use_second_uom = True
>>> product_w_2uom.save()
>>> LotType = Model.get('stock.lot.type')
>>> template = ProductTemplate()
>>> template.name = 'Product'
>>> template.default_uom = kg
>>> template.second_uom = unit
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.salable = True
>>> template.list_price = Decimal('300')
>>> template.cost_price = Decimal('80')
>>> template.cost_price_method = 'average'
>>> template.account_expense = expense
>>> template.account_revenue = revenue
>>> for lot_type in LotType.find([]):
... template.lot_required.append(lot_type)
>>> template.save()
>>> product_lot_wo_2uom, = template.products
>>> product_lot_w_2uom = template.products.new()
>>> product_lot_w_2uom.use_second_uom = True
>>> product_lot_w_2uom.save()
Purchase products two month ago::
>>> Purchase = Model.get('purchase.purchase')
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.date = last_month - relativedelta(months=1)
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'manual'
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product_wo_2uom
>>> purchase_line.quantity = 100
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product_w_2uom
>>> purchase_line.quantity = 200
>>> purchase_line.second_quantity = 10
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product_lot_wo_2uom
>>> purchase_line.quantity = 25
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product_lot_w_2uom
>>> purchase_line.quantity = 75
>>> purchase_line.second_quantity = 6
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.click('process')
>>> purchase.state
u'processing'
>>> len(purchase.moves), len(purchase.shipment_returns)
(4, 0)
>>> for move in purchase.moves:
... if move.product in (product_wo_2uom, product_lot_wo_2uom):
... (move.second_uom == None, move.second_quantity == None)
... elif move.product == product_w_2uom:
... (move.second_uom == unit, move.second_quantity == 10)
... elif move.product == product_lot_w_2uom:
... (move.second_uom == unit, move.second_quantity == 6)
(True, True)
(True, True)
(True, True)
(True, True)
Validate Shipments one month ago::
>>> ShipmentIn = Model.get('stock.shipment.in')
>>> Move = Model.get('stock.move')
>>> Lot = Model.get('stock.lot')
>>> shipment_in = ShipmentIn()
>>> shipment_in.supplier = supplier
>>> shipment_in.effective_date = last_month
>>> for move in purchase.moves:
... incoming_move = Move(id=move.id)
... if move.product == product_lot_wo_2uom:
... lot_wo_2uom = Lot(
... product=product_lot_wo_2uom,
... number=str(product_lot_wo_2uom.id))
... lot_wo_2uom.save()
... incoming_move.lot = lot_wo_2uom
... elif move.product == product_lot_w_2uom:
... lot_w_2uom = Lot(
... product=product_lot_w_2uom,
... number=str(product_lot_wo_2uom.id))
... lot_w_2uom.save()
... incoming_move.lot = lot_w_2uom
... shipment_in.incoming_moves.append(incoming_move)
>>> shipment_in.save()
>>> shipment_in.click('receive')
>>> shipment_in.click('done')
Check available quantities by product::
>>> with config.set_context({'locations': [storage_loc.id], 'stock_date_end': today}):
... product_wo_2uom.reload()
... product_wo_2uom.quantity
... product_wo_2uom.second_quantity
... product_w_2uom.reload()
... product_w_2uom.quantity
... product_w_2uom.second_quantity
... product_lot_wo_2uom.reload()
... product_lot_wo_2uom.quantity
... product_lot_wo_2uom.second_quantity
... product_lot_w_2uom.reload()
... product_lot_w_2uom.quantity
... product_lot_w_2uom.second_quantity
100.0
0.0
200.0
10.0
25.0
0.0
75.0
6.0
Check available quantities by lot::
>>> with config.set_context({'locations': [storage_loc.id], 'stock_date_end': today}):
... lot_wo_2uom.reload()
... lot_wo_2uom.quantity
... lot_wo_2uom.second_quantity
... lot_w_2uom.reload()
... lot_w_2uom.quantity
... lot_w_2uom.second_quantity
25.0
0.0
75.0
6.0
Create an inventory::
>>> Inventory = Model.get('stock.inventory')
>>> inventory = Inventory()
>>> inventory.date = last_month + relativedelta(days=5)
>>> inventory.location = storage_loc
>>> inventory.save()
>>> inventory.click('complete_lines')
>>> len(inventory.lines)
4
>>> for line in inventory.lines:
... if line.product == product_wo_2uom:
... line.expected_quantity == 100.0
... line.second_expected_quantity == 0.0
... line.quantity = 80.0
... elif line.product == product_w_2uom:
... line.expected_quantity == 200.0
... line.second_expected_quantity == 10.0
... line.quantity = 190.0
... line.second_quantity = 8
... elif line.product == product_lot_wo_2uom and line.lot == lot_wo_2uom:
... line.expected_quantity == 25.0
... line.second_expected_quantity == 0.0
... line.quantity = 30.0
... elif line.product == product_lot_w_2uom and line.lot == lot_w_2uom:
... line.expected_quantity == 75.0
... line.second_expected_quantity == 6.0
... line.quantity = 85.0
... line.second_quantity = 7
True
True
True
True
True
True
True
True
>>> inventory.save()
>>> inventory.click('confirm')
Check available quantities::
>>> with config.set_context({'locations': [storage_loc.id], 'stock_date_end': today}):
... product_wo_2uom.reload()
... product_wo_2uom.quantity
... product_wo_2uom.second_quantity
... product_w_2uom.reload()
... product_w_2uom.quantity
... product_w_2uom.second_quantity
... product_lot_wo_2uom.reload()
... product_lot_wo_2uom.quantity
... product_lot_wo_2uom.second_quantity
... product_lot_w_2uom.reload()
... product_lot_w_2uom.quantity
... product_lot_w_2uom.second_quantity
... lot_wo_2uom.reload()
... lot_wo_2uom.quantity
... lot_wo_2uom.second_quantity
... lot_w_2uom.reload()
... lot_w_2uom.quantity
... lot_w_2uom.second_quantity
80.0
0.0
190.0
8.0
30.0
0.0
85.0
7.0
30.0
0.0
85.0
7.0
Create a period::
>>> Period = Model.get('stock.period')
>>> period = Period()
>>> period.date = last_month + relativedelta(days=10)
>>> period.company = company
>>> period.save()
>>> period.click('close')
>>> period.reload()
>>> for cache in period.caches:
... if (cache.product == product_wo_2uom
... and cache.location == storage_loc):
... cache.internal_quantity == 80.0
... cache.second_internal_quantity == 0.0
... elif (cache.product == product_w_2uom
... and cache.location == storage_loc):
... cache.internal_quantity == 190.0
... cache.second_internal_quantity == 8
... elif (cache.product == product_lot_wo_2uom
... and cache.location == storage_loc):
... cache.internal_quantity == 30.0
... cache.second_internal_quantity == 0.0
... elif (cache.product == product_lot_w_2uom
... and cache.location == storage_loc):
... cache.internal_quantity == 85.0
... cache.second_internal_quantity == 7
True
True
True
True
True
True
True
True
Check available quantities::
>>> with config.set_context({'locations': [storage_loc.id], 'stock_date_end': today}):
... product_wo_2uom.reload()
... product_wo_2uom.quantity
... product_wo_2uom.second_quantity
... product_w_2uom.reload()
... product_w_2uom.quantity
... product_w_2uom.second_quantity
... product_lot_wo_2uom.reload()
... product_lot_wo_2uom.quantity
... product_lot_wo_2uom.second_quantity
... product_lot_w_2uom.reload()
... product_lot_w_2uom.quantity
... product_lot_w_2uom.second_quantity
... lot_wo_2uom.reload()
... lot_wo_2uom.quantity
... lot_wo_2uom.second_quantity
... lot_w_2uom.reload()
... lot_w_2uom.quantity
... lot_w_2uom.second_quantity
80.0
0.0
190.0
8.0
30.0
0.0
85.0
7.0
30.0
0.0
85.0
7.0
Create an inventory decreasing quantity in main UoM and increasing in second
UoM::
>>> Inventory = Model.get('stock.inventory')
>>> inventory = Inventory()
>>> inventory.date = last_month + relativedelta(days=15)
>>> inventory.location = storage_loc
>>> inventory.save()
>>> inventory.click('complete_lines')
>>> len(inventory.lines)
4
>>> for line in inventory.lines:
... if line.product == product_w_2uom:
... line.quantity = 180.0
... line.second_quantity = 9
... elif line.product == product_lot_w_2uom:
... line.quantity = 90.0
... line.second_quantity = 5
>>> inventory.save()
>>> inventory.click('confirm')
>>> inventory.reload()
>>> inventory_moves = [m for l in inventory.lines for m in l.moves]
>>> len(inventory_moves)
2
>>> for move in inventory_moves:
... if move.product == product_w_2uom:
... move.quantity == 10.0
... move.second_quantity == -1.0
... elif move.product == product_lot_w_2uom:
... move.quantity == 5.0
... move.second_quantity == -2.0
True
True
True
True
Sale products::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.date = last_month + relativedelta(days=18)
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'manual'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product_wo_2uom
>>> sale_line.quantity = 40.0
>>> sale_line = sale.lines.new()
>>> sale_line.product = product_w_2uom
>>> sale_line.quantity = 30.0
>>> sale_line.second_quantity = 2
>>> sale_line = sale.lines.new()
>>> sale_line.product = product_lot_wo_2uom
>>> sale_line.quantity = 10.0
>>> sale_line = sale.lines.new()
>>> sale_line.product = product_lot_w_2uom
>>> sale_line.quantity = 80.0
>>> sale_line.second_quantity = 4
>>> sale.save()
>>> Sale.quote([sale.id], config.context)
>>> Sale.confirm([sale.id], config.context)
>>> Sale.process([sale.id], config.context)
>>> sale.state
u'processing'
>>> sale.reload()
>>> len(sale.shipments), len(sale.shipment_returns), len(sale.moves)
(1, 0, 4)
>>> for move in sale.moves:
... if move.product in (product_wo_2uom, product_lot_wo_2uom):
... move.second_uom == None and move.second_quantity == None
... elif move.product == product_w_2uom:
... move.second_uom == unit and move.second_quantity == 2
... elif move.product == product_lot_w_2uom:
... move.second_uom == unit and move.second_quantity == 4
True
True
True
True
Check sale shpiment inventory moves::
>>> shipment_out, = sale.shipments
>>> len(shipment_out.inventory_moves)
4
>>> for move in shipment_out.inventory_moves:
... if move.product == product_wo_2uom:
... (move.second_uom == None, move.second_quantity == None)
... elif move.product == product_w_2uom:
... (move.second_uom == unit, move.second_quantity == 2)
... elif move.product == product_lot_wo_2uom:
... (move.second_uom == None, move.second_quantity == None)
... move.lot = lot_wo_2uom
... elif move.product == product_lot_w_2uom:
... (move.second_uom == unit, move.second_quantity == 4)
... move.lot = lot_w_2uom
(True, True)
(True, True)
(True, True)
(True, True)
>>> shipment_out.save()
Assign sale shipment::
>>> shipment_out.click('assign_try')
True
.. TODO Check available quantities and forecast quantities::
..
.. >>> with config.set_context({'locations': [storage_loc.id], 'stock_date_end': today}):
.. ... product_wo_2uom.reload()
.. ... product_wo_2uom.quantity
.. ... product_wo_2uom.second_quantity
.. ... product_wo_2uom.forecast_quantity
.. ... product_wo_2uom.second_forecast_quantity
.. ... product_w_2uom.reload()
.. ... product_w_2uom.quantity
.. ... product_w_2uom.second_quantity
.. ... product_w_2uom.forecast_quantity
.. ... product_w_2uom.second_forecast_quantity
.. ... product_lot_wo_2uom.reload()
.. ... product_lot_wo_2uom.quantity
.. ... product_lot_wo_2uom.second_quantity
.. ... product_lot_wo_2uom.forecast_quantity
.. ... product_lot_wo_2uom.second_forecast_quantity
.. ... product_lot_w_2uom.reload()
.. ... product_lot_w_2uom.quantity
.. ... product_lot_w_2uom.second_quantity
.. ... product_lot_w_2uom.forecast_quantity
.. ... product_lot_w_2uom.second_forecast_quantity
.. ... lot_wo_2uom.reload()
.. ... lot_wo_2uom.quantity
.. ... lot_wo_2uom.second_quantity
.. ... lot_wo_2uom.forecast_quantity
.. ... lot_wo_2uom.second_forecast_quantity
.. ... lot_w_2uom.reload()
.. ... lot_w_2uom.quantity
.. ... lot_w_2uom.second_quantity
.. ... lot_w_2uom.forecast_quantity
.. ... lot_w_2uom.second_forecast_quantity
.. 80.0
.. 0.0
.. 40.0
.. 0.0
.. 180.0
.. 9.0
.. 150.0
.. 7.0
.. 30.0
.. 0.0
.. 20.0
.. 0.0
.. 90.0
.. 5.0
.. 10.0
.. 1.0
.. 30.0
.. 0.0
.. 20.0
.. 0.0
.. 90.0
.. 5.0
.. 10.0
.. 1.0
Finalize the shipment::
>>> shipment_out.reload()
>>> shipment_out.click('pack')
>>> shipment_out.reload()
>>> shipment_out.click('done')
Create return sale::
>>> return_sale = Wizard('sale.return_sale', [sale])
>>> return_sale.execute('return_')
>>> returned_sale, = Sale.find([
... ('state', '=', 'draft'),
... ])
>>> sorted([(x.quantity, x.second_quantity) for x in returned_sale.lines])
[(-80.0, -4.0), (-40.0, None), (-30.0, -2.0), (-10.0, None)]
>>> for sale_line in returned_sale.lines:
... if sale_line.product == product_wo_2uom:
... sale_line.quantity = -25
... elif sale_line.product == product_w_2uom:
... sale_line.quantity = -15
... sale_line.second_quantity = -1
... elif sale_line.product == product_lot_wo_2uom:
... sale_line.quantity = -2
... elif sale_line.product == product_lot_w_2uom:
... sale_line.quantity = -10
... sale_line.second_quantity = -1
>>> returned_sale.save()
>>> returned_sale.click('quote')
>>> returned_sale.click('confirm')
>>> returned_sale.click('process')
>>> returned_sale.state
u'processing'
>>> len(returned_sale.shipments), len(returned_sale.shipment_returns)
(0, 1)
Validate return shipment::
>>> shipment_return, = returned_sale.shipment_returns
>>> for move in shipment_return.incoming_moves:
... if move.product == product_wo_2uom:
... move.second_quantity == None
... elif move.product == product_w_2uom:
... move.second_quantity == 1
... elif move.product == product_lot_wo_2uom:
... move.second_quantity == None
... move.lot = lot_wo_2uom
... elif move.product == product_lot_w_2uom:
... move.second_quantity == 1
... move.lot = lot_w_2uom
True
True
True
True
>>> shipment_return.save()
>>> shipment_return.click('receive')
>>> shipment_return.click('done')
Check available quantities::
>>> with config.set_context({'locations': [storage_loc.id], 'stock_date_end': today}):
... product_wo_2uom.reload()
... product_wo_2uom.quantity
... product_wo_2uom.second_quantity
... product_w_2uom.reload()
... product_w_2uom.quantity
... product_w_2uom.second_quantity
... product_lot_wo_2uom.reload()
... product_lot_wo_2uom.quantity
... product_lot_wo_2uom.second_quantity
... product_lot_w_2uom.reload()
... product_lot_w_2uom.quantity
... product_lot_w_2uom.second_quantity
... lot_wo_2uom.reload()
... lot_wo_2uom.quantity
... lot_wo_2uom.second_quantity
... lot_w_2uom.reload()
... lot_w_2uom.quantity
... lot_w_2uom.second_quantity
65.0
0.0
165.0
8.0
22.0
0.0
20.0
2.0
22.0
0.0
20.0
2.0

View File

@ -28,4 +28,8 @@ def suite():
suite.addTests(doctest.DocFileSuite('scenario_stock_second_uom.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite(
'scenario_stock_second_uom_extras_depend.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite

View File

@ -5,6 +5,10 @@ depends:
extras_depend:
product_raw_variant
stock_lot
sale
purchase
xml:
product.xml
stock.xml
sale.xml
purchase.xml

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook/page[@id='general']/field[@name='unit']"
position="after">
<newline/>
<label name="second_quantity"/>
<field name="second_quantity"/>
<label name="second_unit"/>
<field name="second_unit"/>
</xpath>
</data>

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='unit']" position="after">
<field name="second_quantity"/>
<field name="second_unit"/>
</xpath>
</data>

13
view/sale_line_form.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook/page[@id='general']/field[@name='unit']"
position="after">
<newline/>
<label name="second_quantity"/>
<field name="second_quantity"/>
<label name="second_unit"/>
<field name="second_unit"/>
</xpath>
</data>

9
view/sale_line_tree.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='unit']" position="after">
<field name="second_quantity"/>
<field name="second_unit"/>
</xpath>
</data>