update to 4.7

This commit is contained in:
?ngel ?lvarez 2018-04-25 15:44:48 +02:00
parent ca643dcc70
commit 8197b17013
25 changed files with 294 additions and 185 deletions

57
.drone.yml Normal file
View File

@ -0,0 +1,57 @@
clone:
hg:
image: plugins/hg
pipeline:
tox:
image: ${IMAGE}
environment:
- CFLAGS=-O0
- DB_CACHE=/cache
- TOX_TESTENV_PASSENV=CFLAGS DB_CACHE
- POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
commands:
- pip install tox
- tox -e "${TOXENV}-${DATABASE}"
notify:
image: drillster/drone-email
from: drone@localhost
host: smtp
port: 25
skip_verify: true
when:
status: [ changed, failure ]
services:
postgresql:
image: postgres
when:
matrix:
DATABASE: postgresql
matrix:
include:
- IMAGE: python:2.7
TOXENV: py27
DATABASE: sqlite
- IMAGE: python:2.7
TOXENV: py27
DATABASE: postgresql
- IMAGE: python:3.4
TOXENV: py34
DATABASE: sqlite
- IMAGE: python:3.4
TOXENV: py34
DATABASE: postgresql
- IMAGE: python:3.5
TOXENV: py35
DATABASE: sqlite
- IMAGE: python:3.5
TOXENV: py35
DATABASE: postgresql
- IMAGE: python:3.6
TOXENV: py36
DATABASE: sqlite
- IMAGE: python:3.6
TOXENV: py36
DATABASE: postgresql

View File

@ -1,20 +1,22 @@
#The COPYRIGHT file at the top level of this repository contains the full # The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms. # copyright notices and license terms.
from trytond.pool import Pool from trytond.pool import Pool
from .plan import * from . import plan
from .configuration import * from . import configuration
def register(): def register():
Pool.register( Pool.register(
Plan, plan.Plan,
PlanBOM, plan.PlanBOM,
PlanProductLine, plan.PlanProductLine,
PlanCostType, plan.PlanCostType,
PlanCost, plan.PlanCost,
Configuration, configuration.Configuration,
CreateBomStart, configuration.ConfigurationProductcostPlan,
plan.CreateBomStart,
module='product_cost_plan', type_='model') module='product_cost_plan', type_='model')
Pool.register( Pool.register(
CreateBom, plan.CreateBom,
module='product_cost_plan', type_='wizard') module='product_cost_plan', type_='wizard')

View File

@ -1,19 +1,41 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of # This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms. # this repository contains the full copyright notices and license terms.
from trytond.model import fields from trytond.model import fields, ModelSQL
from trytond.pyson import Eval from trytond.pyson import Eval
from trytond.pool import PoolMeta from trytond.pool import PoolMeta, Pool
from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
__all__ = ['Configuration'] __all__ = ['Configuration', 'ConfigurationProductcostPlan']
__metaclass__ = PoolMeta
class Configuration: class Configuration(CompanyMultiValueMixin):
__name__ = 'production.configuration' __name__ = 'production.configuration'
__metaclass__ = PoolMeta
product_cost_plan_sequence = fields.Property(fields.Many2One('ir.sequence', product_cost_plan_sequence = fields.MultiValue(
fields.Many2One('ir.sequence',
'Product Cost Plan Sequence', domain=[ 'Product Cost Plan Sequence', domain=[
('company', 'in', ('company', 'in',
[Eval('context', {}).get('company', -1), None]), [Eval('context', {}).get('company', -1), None]),
('code', '=', 'product_cost_plan'), ('code', '=', 'product_cost_plan'),
], required=True)) ], required=True))
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {'product_cost_plan_sequence'}:
return pool.get('production.configuration.cost_plan')
return super(Configuration, cls).multivalue_model(field)
class ConfigurationProductcostPlan(ModelSQL, CompanyValueMixin):
"Production Configuration Cost Plan"
__name__ = 'production.configuration.cost_plan'
product_cost_plan_sequence = fields.Many2One('ir.sequence',
'Product Cost Plan Sequence', domain=[
('company', 'in',
[Eval('context', {}).get('company', -1), None]),
('code', '=', 'product_cost_plan'),
], required=True)

View File

@ -5,15 +5,8 @@ this repository contains the full copyright notices and license terms. -->
<data> <data>
<record model="ir.ui.view" id="production_configuration_view_form"> <record model="ir.ui.view" id="production_configuration_view_form">
<field name="model">production.configuration</field> <field name="model">production.configuration</field>
<field name="type" eval="None"/>
<field name="inherit" ref="production.production_configuration_view_form"/>
<field name="name">configuration_form</field> <field name="name">configuration_form</field>
</record> <field name="inherit" ref="production.production_configuration_view_form"/>
<record model="ir.property" id="property_product_cost_plan_sequence">
<field name="field"
search="[('model.model', '=', 'production.configuration'), ('name', '=', 'product_cost_plan_sequence')]"/>
<field name="value" eval="'ir.sequence,' + str(ref('sequence_product_cost_plan'))"/>
</record> </record>
</data> </data>
</tryton> </tryton>

62
plan.py
View File

@ -4,15 +4,15 @@ from decimal import Decimal
from trytond.config import config from trytond.config import config
from trytond.model import ModelSQL, ModelView, fields from trytond.model import ModelSQL, ModelView, fields
from trytond.modules.product import TemplateFunction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, Bool, If from trytond.pyson import Eval, Bool, If
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, StateAction, Button from trytond.wizard import Wizard, StateView, StateAction, Button
price_digits = (16, config.getint('product', 'price_decimal', default=4))
__all__ = ['PlanCostType', 'Plan', 'PlanBOM', 'PlanProductLine', 'PlanCost', __all__ = ['PlanCostType', 'Plan', 'PlanBOM', 'PlanProductLine', 'PlanCost',
'CreateBomStart', 'CreateBom'] 'CreateBomStart', 'CreateBom']
DIGITS = (16, config.getint('product', 'price_decimal', default=4))
class PlanCostType(ModelSQL, ModelView): class PlanCostType(ModelSQL, ModelView):
@ -64,14 +64,14 @@ class Plan(ModelSQL, ModelView):
depends=['costs']), depends=['costs']),
'get_products_tree', setter='set_products_tree') 'get_products_tree', setter='set_products_tree')
products_cost = fields.Function(fields.Numeric('Products Cost', products_cost = fields.Function(fields.Numeric('Products Cost',
digits=DIGITS), digits=price_digits),
'get_products_cost') 'get_products_cost')
costs = fields.One2Many('product.cost.plan.cost', 'plan', 'Costs') costs = fields.One2Many('product.cost.plan.cost', 'plan', 'Costs')
product_cost_price = fields.Function(fields.Numeric('Product Cost Price', product_cost_price = fields.Function(fields.Numeric('Product Cost Price',
digits=DIGITS), digits=price_digits),
'on_change_with_product_cost_price') 'on_change_with_product_cost_price')
cost_price = fields.Function(fields.Numeric('Unit Cost Price', cost_price = fields.Function(fields.Numeric('Unit Cost Price',
digits=DIGITS), digits=price_digits),
'get_cost_price') 'get_cost_price')
notes = fields.Text('Notes') notes = fields.Text('Notes')
@ -121,7 +121,9 @@ class Plan(ModelSQL, ModelView):
self.bom = None self.bom = None
if self.product: if self.product:
self.name = self.product.rec_name self.name = self.product.rec_name
self.bom = self.on_change_with_bom() bom = self.on_change_with_bom()
self.bom = bom
self.boms = [x[1] for x in self.find_boms()]
if self.product: if self.product:
self.uom = self.product.default_uom self.uom = self.product.default_uom
@ -145,6 +147,19 @@ class Plan(ModelSQL, ModelView):
if boms: if boms:
return boms[0].id return boms[0].id
def find_boms(self, inputs=None):
res = []
if not self.bom:
return res
if not inputs:
inputs = self.bom.inputs
for input_ in inputs:
if input_.product.boms:
product_bom = input_.product.boms[0].bom
res.append((input_.product.id, product_bom.id))
res += self.find_boms(product_bom.inputs)
return res
@fields.depends('bom', 'boms', 'product') @fields.depends('bom', 'boms', 'product')
def on_change_with_boms(self): def on_change_with_boms(self):
boms = { boms = {
@ -154,16 +169,7 @@ class Plan(ModelSQL, ModelView):
if not self.bom: if not self.bom:
return boms return boms
def find_boms(inputs): products = set(self.find_boms())
res = []
for input_ in inputs:
if input_.product.boms:
product_bom = input_.product.boms[0].bom
res.append((input_.product.id, product_bom.id))
res += find_boms(product_bom.inputs)
return res
products = set(find_boms(self.bom.inputs))
for index, (product_id, _) in enumerate(products): for index, (product_id, _) in enumerate(products):
boms['add'].append((index, { boms['add'].append((index, {
'product': product_id, 'product': product_id,
@ -339,10 +345,7 @@ class Plan(ModelSQL, ModelView):
assert self.product assert self.product
cost_price = Uom.compute_price(self.uom, self.cost_price, cost_price = Uom.compute_price(self.uom, self.cost_price,
self.product.default_uom) self.product.default_uom)
if (hasattr(self.product.__class__, 'cost_price') and not if hasattr(self.product.__class__, 'cost_price'):
isinstance(self.product.__class__.cost_price, TemplateFunction)
):
digits = self.product.__class__.cost_price.digits[1] digits = self.product.__class__.cost_price.digits[1]
cost_price = cost_price.quantize(Decimal(str(10 ** -digits))) cost_price = cost_price.quantize(Decimal(str(10 ** -digits)))
self.product.cost_price = cost_price self.product.cost_price = cost_price
@ -519,18 +522,18 @@ class PlanProductLine(ModelSQL, ModelView):
party_stock = fields.Boolean('Party Stock', party_stock = fields.Boolean('Party Stock',
help='Use stock owned by party instead of company stock.') help='Use stock owned by party instead of company stock.')
product_cost_price = fields.Numeric('Product Cost Price', product_cost_price = fields.Numeric('Product Cost Price',
digits=DIGITS, digits=price_digits,
states={ states={
'readonly': True, 'readonly': True,
}, depends=['product']) }, depends=['product'])
cost_price = fields.Numeric('Cost Price', required=True, cost_price = fields.Numeric('Cost Price', required=True,
digits=DIGITS) digits=price_digits)
unit_cost = fields.Function(fields.Numeric('Unit Cost', unit_cost = fields.Function(fields.Numeric('Unit Cost',
digits=DIGITS, digits=price_digits,
help="The cost of this product for each unit of plan's product."), help="The cost of this product for each unit of plan's product."),
'get_unit_cost') 'get_unit_cost')
total_cost = fields.Function(fields.Numeric('Total Cost', total_cost = fields.Function(fields.Numeric('Total Cost',
digits=DIGITS, digits=price_digits,
help="The cost of this product for total plan's quantity."), help="The cost of this product for total plan's quantity."),
'get_total_cost') 'get_total_cost')
@ -561,7 +564,6 @@ class PlanProductLine(ModelSQL, ModelView):
if self.product.may_belong_to_party: if self.product.may_belong_to_party:
zero_cost_price = True zero_cost_price = True
self.uom = self.product.default_uom.id self.uom = self.product.default_uom.id
self.uom.rec_name = self.product.default_uom.rec_name
self.product_cost_price = self.product.cost_price self.product_cost_price = self.product.cost_price
if zero_cost_price: if zero_cost_price:
self.cost_price = Decimal('0.0') self.cost_price = Decimal('0.0')
@ -571,7 +573,6 @@ class PlanProductLine(ModelSQL, ModelView):
self.name = None self.name = None
self.party_stock = False self.party_stock = False
self.uom = None self.uom = None
self.uom.rec_name = ''
self.product_cost_price = None self.product_cost_price = None
@fields.depends('children', '_parent_plan.uom', 'product', 'uom', 'plan') @fields.depends('children', '_parent_plan.uom', 'product', 'uom', 'plan')
@ -584,7 +585,7 @@ class PlanProductLine(ModelSQL, ModelView):
if self.product: if self.product:
return self.product.default_uom.category.id return self.product.default_uom.category.id
@fields.depends('uom', 'product') @fields.depends('uom')
def on_change_with_uom_digits(self, name=None): def on_change_with_uom_digits(self, name=None):
if self.uom: if self.uom:
return self.uom.digits return self.uom.digits
@ -596,7 +597,8 @@ class PlanProductLine(ModelSQL, ModelView):
if self.party_stock: if self.party_stock:
self.cost_price = Decimal('0.0') self.cost_price = Decimal('0.0')
if self.cost_price is None and self.product and self.uom: return
if not self.cost_price and self.product and self.uom:
digits = self.__class__.cost_price.digits[1] digits = self.__class__.cost_price.digits[1]
cost = UoM.compute_price(self.product.default_uom, cost = UoM.compute_price(self.product.default_uom,
self.product.cost_price, self.uom) self.product.cost_price, self.uom)
@ -693,9 +695,9 @@ class PlanCost(ModelSQL, ModelView):
('system', '=', Eval('system')), ('system', '=', Eval('system')),
], ],
required=True, states=STATES, depends=DEPENDS) required=True, states=STATES, depends=DEPENDS)
internal_cost = fields.Numeric('Cost (Internal Use)', digits=DIGITS, internal_cost = fields.Numeric('Cost (Internal Use)', digits=price_digits,
readonly=True) readonly=True)
cost = fields.Function(fields.Numeric('Cost', digits=DIGITS, cost = fields.Function(fields.Numeric('Cost', digits=price_digits,
required=True, states=STATES, depends=DEPENDS), required=True, states=STATES, depends=DEPENDS),
'get_cost', setter='set_cost') 'get_cost', setter='set_cost')
system = fields.Boolean('System Managed', readonly=True) system = fields.Boolean('System Managed', readonly=True)

View File

@ -4,44 +4,83 @@
from setuptools import setup from setuptools import setup
import re import re
import os import os
import ConfigParser import io
try:
from configparser import ConfigParser
except ImportError:
from ConfigParser import ConfigParser
MODULE = 'product_cost_plan' MODULE = 'product_cost_plan'
PREFIX = 'nantic' PREFIX = 'nantic'
MODULE2PREFIX = {} MODULE2PREFIX = {
'production_external_party': 'nantic',
}
def read(fname): def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read() return io.open(
os.path.join(os.path.dirname(__file__), fname),
'r', encoding='utf-8').read()
config = ConfigParser.ConfigParser()
def get_require_version(name):
if minor_version % 2:
require = '%s >= %s.%s.dev0, < %s.%s'
else:
require = '%s >= %s.%s, < %s.%s'
require %= (name, major_version, minor_version,
major_version, minor_version + 1)
return require
config = ConfigParser()
config.readfp(open('tryton.cfg')) config.readfp(open('tryton.cfg'))
info = dict(config.items('tryton')) info = dict(config.items('tryton'))
for key in ('depends', 'extras_depend', 'xml'): for key in ('depends', 'extras_depend', 'xml'):
if key in info: if key in info:
info[key] = info[key].strip().splitlines() info[key] = info[key].strip().splitlines()
major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
version = info.get('version', '0.0.1')
major_version, minor_version, _ = version.split('.', 2)
major_version = int(major_version) major_version = int(major_version)
minor_version = int(minor_version) minor_version = int(minor_version)
requires = [] requires = []
for dep in info.get('depends', []): for dep in info.get('depends', []) + ['production_external_party']:
if not re.match(r'(ir|res|webdav)(\W|$)', dep): if not re.match(r'(ir|res)(\W|$)', dep):
prefix = MODULE2PREFIX.get(dep, 'trytond') prefix = MODULE2PREFIX.get(dep, 'trytond')
requires.append('%s_%s >= %s.%s, < %s.%s' % requires.append(get_require_version('%s_%s' % (prefix, dep)))
(prefix, dep, major_version, minor_version, requires.append(get_require_version('trytond'))
major_version, minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' %
(major_version, minor_version, major_version, minor_version + 1))
tests_require = ['proteus >= %s.%s, < %s.%s' % tests_require = [
(major_version, minor_version, major_version, minor_version + 1)] get_require_version('proteus'),
get_require_version('nantic-production_external_party')
]
series = '%s.%s' % (major_version, minor_version)
if minor_version % 2:
branch = 'default'
else:
branch = series
dependency_links = [
('hg+https://bitbucket.org/nantic/'
'trytond-production_external_party@%(branch)s'
'#egg=nantic-production_external_party-%(series)s' % {
'branch': branch,
'series': series,
}),
]
if minor_version % 2:
# Add development index for testing with proteus
dependency_links.append('https://trydevpi.tryton.org/')
setup(name='%s_%s' % (PREFIX, MODULE), setup(name='%s_%s' % (PREFIX, MODULE),
version=info.get('version', '0.0.1'), version=version,
description='Product Cost Plan', description='',
long_description=read('README'), long_description=read('README'),
author='NaN·tic', author='NaN·tic',
author_email='info@nan-tic.com',
url='http://www.nan-tic.com/', url='http://www.nan-tic.com/',
download_url="https://bitbucket.org/nantic/trytond-%s" % MODULE, download_url="https://bitbucket.org/nantic/trytond-%s" % MODULE,
package_dir={'trytond.modules.%s' % MODULE: '.'}, package_dir={'trytond.modules.%s' % MODULE: '.'},
@ -51,7 +90,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
], ],
package_data={ package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', []) 'trytond.modules.%s' % MODULE: (info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'view/*.xml', 'tests/*.rst']), + ['tryton.cfg', 'locale/*.po', 'tests/*.rst']),
}, },
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
@ -71,12 +110,17 @@ setup(name='%s_%s' % (PREFIX, MODULE),
'Natural Language :: Russian', 'Natural Language :: Russian',
'Natural Language :: Spanish', 'Natural Language :: Spanish',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Office/Business', 'Topic :: Office/Business',
], ],
license='GPL-3', license='GPL-3',
install_requires=requires, install_requires=requires,
dependency_links=dependency_links,
zip_safe=False, zip_safe=False,
entry_points=""" entry_points="""
[trytond.modules] [trytond.modules]
@ -85,4 +129,9 @@ setup(name='%s_%s' % (PREFIX, MODULE),
test_suite='tests', test_suite='tests',
test_loader='trytond.test_loader:Loader', test_loader='trytond.test_loader:Loader',
tests_require=tests_require, tests_require=tests_require,
use_2to3=True,
convert_2to3_doctests=[
'tests/scenario_account_invoice_discount.rst',
'tests/scenario_account_invoice_information_uom.rst',
],
) )

View File

@ -1,4 +1,10 @@
# The COPYRIGHT file at the top level of this repository contains the full # The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms. # copyright notices and license terms.
from .test_product_cost_plan import suite try:
from trytond.modules.account_invoice.tests.test_product_cost_plan import (
suite)
except ImportError:
from .test_product_cost_plan import suite
__all__ = ['suite']

View File

@ -13,44 +13,22 @@ Imports::
>>> from decimal import Decimal >>> from decimal import Decimal
>>> from proteus import config, Model, Wizard >>> from proteus import config, Model, Wizard
>>> today = datetime.date.today() >>> today = datetime.date.today()
>>> from trytond.tests.tools import activate_modules
Create database:: >>> from trytond.modules.company.tests.tools import create_company, \
... get_company
>>> config = config.set_trytond()
>>> config.pool.test = True
Install product_cost_plan Module:: Install product_cost_plan Module::
>>> Module = Model.get('ir.module') >>> config = activate_modules('product_cost_plan')
>>> modules = Module.find([('name', '=', 'product_cost_plan')])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.install_upgrade').execute('upgrade')
Create company:: Create company::
>>> Currency = Model.get('currency.currency') >>> _ = create_company()
>>> CurrencyRate = Model.get('currency.currency.rate') >>> company = get_company()
>>> Company = Model.get('company.company') >>> tax_identifier = company.party.identifiers.new()
>>> Party = Model.get('party.party') >>> tax_identifier.type = 'eu_vat'
>>> company_config = Wizard('company.company.config') >>> tax_identifier.code = 'BE0897290877'
>>> company_config.execute('company') >>> company.party.save()
>>> 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:: Reload the context::
@ -64,15 +42,18 @@ Create product::
>>> ProductTemplate = Model.get('product.template') >>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate() >>> template = ProductTemplate()
>>> template.name = 'product' >>> template.name = 'product'
>>> template.producible = True
>>> template.default_uom = unit >>> template.default_uom = unit
>>> template.type = 'goods' >>> template.type = 'goods'
>>> template.list_price = Decimal(30) >>> template.list_price = Decimal(30)
>>> template.cost_price = Decimal(20)
>>> template.save() >>> template.save()
>>> product, = template.products >>> product, = template.products
>>> product.cost_price = Decimal(20)
>>> product.save()
>>> template = ProductTemplate() >>> template = ProductTemplate()
>>> template.name = 'product 2' >>> template.name = 'product 2'
>>> template.producible = True
>>> template.default_uom = unit >>> template.default_uom = unit
>>> template.type = 'goods' >>> template.type = 'goods'
>>> template.list_price = Decimal(30) >>> template.list_price = Decimal(30)
@ -82,6 +63,7 @@ Create product::
>>> template = ProductTemplate() >>> template = ProductTemplate()
>>> template.name = 'product 3' >>> template.name = 'product 3'
>>> template.producible = True
>>> template.default_uom = unit >>> template.default_uom = unit
>>> template.type = 'goods' >>> template.type = 'goods'
>>> template.list_price = Decimal(15) >>> template.list_price = Decimal(15)
@ -98,36 +80,41 @@ Create Components::
>>> templateA.default_uom = meter >>> templateA.default_uom = meter
>>> templateA.type = 'goods' >>> templateA.type = 'goods'
>>> templateA.list_price = Decimal(2) >>> templateA.list_price = Decimal(2)
>>> templateA.cost_price = Decimal(1)
>>> templateA.save() >>> templateA.save()
>>> componentA, = templateA.products >>> componentA, = templateA.products
>>> componentA.cost_price = Decimal(1)
>>> componentA.save()
>>> templateB = ProductTemplate() >>> templateB = ProductTemplate()
>>> templateB.name = 'component B' >>> templateB.name = 'component B'
>>> templateB.default_uom = meter >>> templateB.default_uom = meter
>>> templateB.type = 'goods' >>> templateB.type = 'goods'
>>> templateB.list_price = Decimal(2) >>> templateB.list_price = Decimal(2)
>>> templateB.cost_price = Decimal(1)
>>> templateB.save() >>> templateB.save()
>>> componentB, = templateB.products >>> componentB, = templateB.products
>>> componentB.cost_price = Decimal(1)
>>> componentB.save()
>>> template1 = ProductTemplate() >>> template1 = ProductTemplate()
>>> template1.name = 'component 1' >>> template1.name = 'component 1'
>>> template1.default_uom = unit >>> template1.default_uom = unit
>>> template1.producible = True
>>> template1.type = 'goods' >>> template1.type = 'goods'
>>> template1.list_price = Decimal(5) >>> template1.list_price = Decimal(5)
>>> template1.cost_price = Decimal(2)
>>> template1.save() >>> template1.save()
>>> component1, = template1.products >>> component1, = template1.products
>>> component1.cost_price = Decimal(2)
>>> component1.save()
>>> template2 = ProductTemplate() >>> template2 = ProductTemplate()
>>> template2.name = 'component 2' >>> template2.name = 'component 2'
>>> template2.default_uom = meter >>> template2.default_uom = meter
>>> template2.type = 'goods' >>> template2.type = 'goods'
>>> template2.list_price = Decimal(7) >>> template2.list_price = Decimal(7)
>>> template2.cost_price = Decimal(5)
>>> template2.save() >>> template2.save()
>>> component2, = template2.products >>> component2, = template2.products
>>> component2.cost_price = Decimal(5)
>>> component2.save()
Create Bill of Material:: Create Bill of Material::
@ -145,7 +132,7 @@ Create Bill of Material::
>>> component_bom.save() >>> component_bom.save()
>>> ProductBom = Model.get('product.product-production.bom') >>> ProductBom = Model.get('product.product-production.bom')
>>> component1.boms.append(ProductBom(bom=component_bom)) >>> component1.boms.append(ProductBom (bom=component_bom))
>>> component1.save() >>> component1.save()
>>> bom = BOM(name='product') >>> bom = BOM(name='product')
@ -169,6 +156,7 @@ Create a cost plan from BoM without child BoMs::
>>> CostPlan = Model.get('product.cost.plan') >>> CostPlan = Model.get('product.cost.plan')
>>> plan = CostPlan() >>> plan = CostPlan()
>>> plan.number = '1'
>>> plan.product = product >>> plan.product = product
>>> plan.bom == bom >>> plan.bom == bom
True True
@ -201,10 +189,10 @@ Create a cost plan from BoM without child BoMs::
>>> cost, = plan.costs >>> cost, = plan.costs
>>> cost.rec_name == 'Raw materials' >>> cost.rec_name == 'Raw materials'
True True
>>> cost.cost == Decimal('17.5')
True
>>> plan.cost_price == Decimal('17.5') >>> plan.cost_price == Decimal('17.5')
True True
>>> cost.cost == Decimal('17.5')
True
Create a manual cost and test total cost is updated:: Create a manual cost and test total cost is updated::
@ -344,4 +332,3 @@ Create BoM from Cost Plan::
True True
>>> plan3.bom.outputs[0].quantity >>> plan3.bom.outputs[0].quantity
2.0 2.0

View File

@ -12,52 +12,25 @@ Imports::
>>> from dateutil.relativedelta import relativedelta >>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal >>> from decimal import Decimal
>>> from proteus import config, Model, Wizard >>> from proteus import config, Model, Wizard
>>> from trytond.tests.tools import activate_modules
>>> today = datetime.date.today()
>>> from trytond.modules.company.tests.tools import create_company, \
... get_company
>>> today = datetime.date.today() >>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install product_cost_plan Module:: Install product_cost_plan Module::
>>> Module = Model.get('ir.module') >>> config = activate_modules(['product_cost_plan', 'production_external_party'])
>>> modules = Module.find([('name', '=', 'product_cost_plan')])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.install_upgrade').execute('upgrade')
Install production_external_party Module::
>>> Module = Model.get('ir.module')
>>> modules = Module.find([('name', '=', 'production_external_party')])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.install_upgrade').execute('upgrade')
Create company:: Create company::
>>> Currency = Model.get('currency.currency') >>> _ = create_company()
>>> CurrencyRate = Model.get('currency.currency.rate') >>> company = get_company()
>>> Company = Model.get('company.company') >>> tax_identifier = company.party.identifiers.new()
>>> Party = Model.get('party.party') >>> tax_identifier.type = 'eu_vat'
>>> company_config = Wizard('company.company.config') >>> tax_identifier.code = 'BE0897290877'
>>> company_config.execute('company') >>> company.party.save()
>>> 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:: Reload the context::
@ -74,16 +47,18 @@ Create product::
>>> template.default_uom = unit >>> template.default_uom = unit
>>> template.type = 'goods' >>> template.type = 'goods'
>>> template.list_price = Decimal(30) >>> template.list_price = Decimal(30)
>>> template.cost_price = Decimal(20) >>> template.producible = True
>>> template.save() >>> template.save()
>>> product, = template.products >>> product, = template.products
>>> product.cost_price = Decimal(20)
>>> product.save()
>>> template = ProductTemplate() >>> template = ProductTemplate()
>>> template.name = 'product 2' >>> template.name = 'product 2'
>>> template.default_uom = unit >>> template.default_uom = unit
>>> template.type = 'goods' >>> template.type = 'goods'
>>> template.list_price = Decimal(30) >>> template.list_price = Decimal(30)
>>> template.cost_price = Decimal(0) >>> template.producible = True
>>> template.save() >>> template.save()
>>> product2, = template.products >>> product2, = template.products
@ -92,7 +67,7 @@ Create product::
>>> template.default_uom = unit >>> template.default_uom = unit
>>> template.type = 'goods' >>> template.type = 'goods'
>>> template.list_price = Decimal(15) >>> template.list_price = Decimal(15)
>>> template.cost_price = Decimal(0) >>> template.producible = True
>>> template.save() >>> template.save()
>>> product3, = template.products >>> product3, = template.products
@ -105,19 +80,21 @@ Create Components::
>>> template1.default_uom = unit >>> template1.default_uom = unit
>>> template1.type = 'goods' >>> template1.type = 'goods'
>>> template1.list_price = Decimal(5) >>> template1.list_price = Decimal(5)
>>> template1.cost_price = Decimal(2)
>>> template1.may_belong_to_party = True >>> template1.may_belong_to_party = True
>>> template1.save() >>> template1.save()
>>> component1, = template1.products >>> component1, = template1.products
>>> component1.cost_price = Decimal(2)
>>> component1.save()
>>> template2 = ProductTemplate() >>> template2 = ProductTemplate()
>>> template2.name = 'component 2' >>> template2.name = 'component 2'
>>> template2.default_uom = meter >>> template2.default_uom = meter
>>> template2.type = 'goods' >>> template2.type = 'goods'
>>> template2.list_price = Decimal(7) >>> template2.list_price = Decimal(7)
>>> template2.cost_price = Decimal(5)
>>> template2.save() >>> template2.save()
>>> component2, = template2.products >>> component2, = template2.products
>>> component2.cost_price = Decimal(5)
>>> component2.save()
Create Bill of Material with party stock for component 1:: Create Bill of Material with party stock for component 1::
@ -145,6 +122,7 @@ Create a cost plan from BoM::
>>> CostPlan = Model.get('product.cost.plan') >>> CostPlan = Model.get('product.cost.plan')
>>> plan = CostPlan() >>> plan = CostPlan()
>>> plan.number = '1'
>>> plan.product = product >>> plan.product = product
>>> plan.bom == bom >>> plan.bom == bom
True True
@ -181,8 +159,6 @@ Set party stock also for second component::
>>> for product_line in plan2.products: >>> for product_line in plan2.products:
... if product_line.product == component2: ... if product_line.product == component2:
... product_line.party_stock = True ... product_line.party_stock = True
... product_line.save()
... product_line.reload()
>>> plan2.save() >>> plan2.save()
>>> plan2.reload() >>> plan2.reload()
>>> sorted([(p.quantity, p.product.rec_name, bool(p.party_stock), p.cost_price) >>> sorted([(p.quantity, p.product.rec_name, bool(p.party_stock), p.cost_price)
@ -284,4 +260,3 @@ Create BoM from Cost Plan::
True True
>>> plan3.bom.outputs[0].quantity >>> plan3.bom.outputs[0].quantity
2.0 2.0

View File

@ -5,26 +5,26 @@ import unittest
import doctest import doctest
import trytond.tests.test_tryton import trytond.tests.test_tryton
from trytond.tests.test_tryton import ModuleTestCase from trytond.tests.test_tryton import ModuleTestCase
from trytond.tests.test_tryton import doctest_setup, doctest_teardown from trytond.tests.test_tryton import doctest_teardown
from trytond.tests.test_tryton import doctest_checker from trytond.tests.test_tryton import doctest_checker
class TestCase(ModuleTestCase): class ProductCostPlanTestCase(ModuleTestCase):
'Test module' 'Test module'
module = 'product_cost_plan' module = 'product_cost_plan'
def suite(): def suite():
suite = trytond.tests.test_tryton.suite() suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCase)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
suite.addTests(doctest.DocFileSuite( ProductCostPlanTestCase))
'scenario_product_cost_plan.rst', suite.addTests(doctest.DocFileSuite('scenario_product_cost_plan.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker, checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite( suite.addTests(
'scenario_product_cost_plan_extras_depend.rst', doctest.DocFileSuite('scenario_product_cost_plan_extras_depend.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker, checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite return suite

15
tox.ini Normal file
View File

@ -0,0 +1,15 @@
[tox]
envlist = {py27,py34,py35,py36}-{sqlite,postgresql},pypy-{sqlite,postgresql}
[testenv]
commands = {envpython} setup.py test
deps =
{py27,py34,py35,py36}-postgresql: psycopg2 >= 2.5
pypy-postgresql: psycopg2cffi >= 2.5
sqlite: sqlitebck
setenv =
sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
sqlite: DB_NAME={env:SQLITE_NAME::memory:}
postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
install_command = pip install --pre --process-dependency-links {opts} {packages}

View File

@ -1,5 +1,6 @@
[tryton] [tryton]
version=4.1.0 version=4.8.0
depends: depends:
production production
extras_depend: extras_depend:

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<form string="Product Cost Plan BOM"> <form>
<label name="plan"/> <label name="plan"/>
<field name="plan"/> <field name="plan"/>
<newline/> <newline/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<tree string="Product Cost Plan BOM" editable="bottom"> <tree editable="bottom">
<field name="plan"/> <field name="plan"/>
<field name="product"/> <field name="product"/>
<field name="bom"/> <field name="bom"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<form string="Product Cost Plan" col="6" cursor="product"> <form col="6" cursor="product">
<label name="number"/> <label name="number"/>
<field name="number"/> <field name="number"/>
<label name="name"/> <label name="name"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<tree string="Product Cost Plan"> <tree>
<field name="number"/> <field name="number"/>
<field name="name"/> <field name="name"/>
<field name="product" tree_invisible="1"/> <field name="product" tree_invisible="1"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<form string="Product Cost Plan Product Line"> <form>
<label name="plan"/> <label name="plan"/>
<field name="plan"/> <field name="plan"/>
<newline/> <newline/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<tree string="Product Cost Plan Product Line" editable="bottom" sequence="sequence"> <tree editable="bottom" sequence="sequence">
<field name="name"/> <field name="name"/>
<field name="product"/> <field name="product"/>
<field name="plan"/> <field name="plan"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<form string="Create BOM"> <form>
<label name="name"/> <label name="name"/>
<field name="name" colspan="3"/> <field name="name" colspan="3"/>
</form> </form>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<form string="Plan Cost"> <form>
<label name="plan"/> <label name="plan"/>
<field name="plan"/> <field name="plan"/>
<newline/> <newline/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<tree string="Plan Cost" editable="bottom" sequence="sequence"> <tree editable="bottom" sequence="sequence">
<field name="plan"/> <field name="plan"/>
<field name="type"/> <field name="type"/>
<field name="cost"/> <field name="cost"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<form string="Plan Cost Type"> <form>
<label name="name"/> <label name="name"/>
<field name="name"/> <field name="name"/>
<label name="system"/> <label name="system"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full <!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. --> copyright notices and license terms. -->
<tree string="Plan Cost Type"> <tree>
<field name="name"/> <field name="name"/>
<field name="system"/> <field name="system"/>
</tree> </tree>