diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..897fc2e --- /dev/null +++ b/.drone.yml @@ -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 diff --git a/__init__.py b/__init__.py index d76e019..266c447 100644 --- a/__init__.py +++ b/__init__.py @@ -1,20 +1,22 @@ -#The COPYRIGHT file at the top level of this repository contains the full -#copyright notices and license terms. +# The COPYRIGHT file at the top level of this repository contains the full +# copyright notices and license terms. from trytond.pool import Pool -from .plan import * -from .configuration import * +from . import plan +from . import configuration + def register(): Pool.register( - Plan, - PlanBOM, - PlanProductLine, - PlanCostType, - PlanCost, - Configuration, - CreateBomStart, + plan.Plan, + plan.PlanBOM, + plan.PlanProductLine, + plan.PlanCostType, + plan.PlanCost, + configuration.Configuration, + configuration.ConfigurationProductcostPlan, + plan.CreateBomStart, module='product_cost_plan', type_='model') Pool.register( - CreateBom, + plan.CreateBom, module='product_cost_plan', type_='wizard') diff --git a/configuration.py b/configuration.py index 5a157e5..95a9314 100644 --- a/configuration.py +++ b/configuration.py @@ -1,19 +1,41 @@ -#This file is part of Tryton. The COPYRIGHT file at the top level of -#this repository contains the full copyright notices and license terms. -from trytond.model import fields +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.model import fields, ModelSQL 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'] -__metaclass__ = PoolMeta +__all__ = ['Configuration', 'ConfigurationProductcostPlan'] -class Configuration: +class Configuration(CompanyMultiValueMixin): __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=[ ('company', 'in', [Eval('context', {}).get('company', -1), None]), ('code', '=', 'product_cost_plan'), ], 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) diff --git a/configuration.xml b/configuration.xml index a0c67d9..a4748ca 100644 --- a/configuration.xml +++ b/configuration.xml @@ -5,15 +5,8 @@ this repository contains the full copyright notices and license terms. --> production.configuration - - configuration_form - - - - - + diff --git a/locale/ca_ES.po b/locale/ca.po similarity index 100% rename from locale/ca_ES.po rename to locale/ca.po diff --git a/locale/es_ES.po b/locale/es.po similarity index 100% rename from locale/es_ES.po rename to locale/es.po diff --git a/plan.py b/plan.py index 38834dd..56d17af 100644 --- a/plan.py +++ b/plan.py @@ -4,15 +4,15 @@ from decimal import Decimal from trytond.config import config from trytond.model import ModelSQL, ModelView, fields -from trytond.modules.product import TemplateFunction from trytond.pool import Pool from trytond.pyson import Eval, Bool, If from trytond.transaction import Transaction from trytond.wizard import Wizard, StateView, StateAction, Button +price_digits = (16, config.getint('product', 'price_decimal', default=4)) + __all__ = ['PlanCostType', 'Plan', 'PlanBOM', 'PlanProductLine', 'PlanCost', 'CreateBomStart', 'CreateBom'] -DIGITS = (16, config.getint('product', 'price_decimal', default=4)) class PlanCostType(ModelSQL, ModelView): @@ -64,14 +64,14 @@ class Plan(ModelSQL, ModelView): depends=['costs']), 'get_products_tree', setter='set_products_tree') products_cost = fields.Function(fields.Numeric('Products Cost', - digits=DIGITS), + digits=price_digits), 'get_products_cost') costs = fields.One2Many('product.cost.plan.cost', 'plan', 'Costs') product_cost_price = fields.Function(fields.Numeric('Product Cost Price', - digits=DIGITS), + digits=price_digits), 'on_change_with_product_cost_price') cost_price = fields.Function(fields.Numeric('Unit Cost Price', - digits=DIGITS), + digits=price_digits), 'get_cost_price') notes = fields.Text('Notes') @@ -121,7 +121,9 @@ class Plan(ModelSQL, ModelView): self.bom = None if self.product: 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: self.uom = self.product.default_uom @@ -145,6 +147,19 @@ class Plan(ModelSQL, ModelView): if boms: 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') def on_change_with_boms(self): boms = { @@ -154,16 +169,7 @@ class Plan(ModelSQL, ModelView): if not self.bom: return boms - def find_boms(inputs): - 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)) + products = set(self.find_boms()) for index, (product_id, _) in enumerate(products): boms['add'].append((index, { 'product': product_id, @@ -339,10 +345,7 @@ class Plan(ModelSQL, ModelView): assert self.product cost_price = Uom.compute_price(self.uom, self.cost_price, self.product.default_uom) - if (hasattr(self.product.__class__, 'cost_price') and not - isinstance(self.product.__class__.cost_price, TemplateFunction) - ): - + if hasattr(self.product.__class__, 'cost_price'): digits = self.product.__class__.cost_price.digits[1] cost_price = cost_price.quantize(Decimal(str(10 ** -digits))) self.product.cost_price = cost_price @@ -519,18 +522,18 @@ class PlanProductLine(ModelSQL, ModelView): party_stock = fields.Boolean('Party Stock', help='Use stock owned by party instead of company stock.') product_cost_price = fields.Numeric('Product Cost Price', - digits=DIGITS, + digits=price_digits, states={ 'readonly': True, }, depends=['product']) cost_price = fields.Numeric('Cost Price', required=True, - digits=DIGITS) + digits=price_digits) 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."), 'get_unit_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."), 'get_total_cost') @@ -561,7 +564,6 @@ class PlanProductLine(ModelSQL, ModelView): if self.product.may_belong_to_party: zero_cost_price = True 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 if zero_cost_price: self.cost_price = Decimal('0.0') @@ -571,7 +573,6 @@ class PlanProductLine(ModelSQL, ModelView): self.name = None self.party_stock = False self.uom = None - self.uom.rec_name = '' self.product_cost_price = None @fields.depends('children', '_parent_plan.uom', 'product', 'uom', 'plan') @@ -584,7 +585,7 @@ class PlanProductLine(ModelSQL, ModelView): if self.product: return self.product.default_uom.category.id - @fields.depends('uom', 'product') + @fields.depends('uom') def on_change_with_uom_digits(self, name=None): if self.uom: return self.uom.digits @@ -596,7 +597,8 @@ class PlanProductLine(ModelSQL, ModelView): if self.party_stock: 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] cost = UoM.compute_price(self.product.default_uom, self.product.cost_price, self.uom) @@ -693,9 +695,9 @@ class PlanCost(ModelSQL, ModelView): ('system', '=', Eval('system')), ], 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) - cost = fields.Function(fields.Numeric('Cost', digits=DIGITS, + cost = fields.Function(fields.Numeric('Cost', digits=price_digits, required=True, states=STATES, depends=DEPENDS), 'get_cost', setter='set_cost') system = fields.Boolean('System Managed', readonly=True) diff --git a/setup.py b/setup.py index fd8ee51..6db88a2 100644 --- a/setup.py +++ b/setup.py @@ -4,44 +4,83 @@ from setuptools import setup import re import os -import ConfigParser +import io +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import ConfigParser MODULE = 'product_cost_plan' PREFIX = 'nantic' -MODULE2PREFIX = {} +MODULE2PREFIX = { + 'production_external_party': 'nantic', + } 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')) info = dict(config.items('tryton')) for key in ('depends', 'extras_depend', 'xml'): if key in info: 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) minor_version = int(minor_version) requires = [] -for dep in info.get('depends', []): - if not re.match(r'(ir|res|webdav)(\W|$)', dep): +for dep in info.get('depends', []) + ['production_external_party']: + if not re.match(r'(ir|res)(\W|$)', dep): prefix = MODULE2PREFIX.get(dep, 'trytond') - requires.append('%s_%s >= %s.%s, < %s.%s' % - (prefix, dep, major_version, minor_version, - major_version, minor_version + 1)) -requires.append('trytond >= %s.%s, < %s.%s' % - (major_version, minor_version, major_version, minor_version + 1)) + requires.append(get_require_version('%s_%s' % (prefix, dep))) +requires.append(get_require_version('trytond')) -tests_require = ['proteus >= %s.%s, < %s.%s' % - (major_version, minor_version, major_version, minor_version + 1)] +tests_require = [ + 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), - version=info.get('version', '0.0.1'), - description='Product Cost Plan', + version=version, + description='', long_description=read('README'), author='NaN·tic', + author_email='info@nan-tic.com', url='http://www.nan-tic.com/', download_url="https://bitbucket.org/nantic/trytond-%s" % MODULE, package_dir={'trytond.modules.%s' % MODULE: '.'}, @@ -51,7 +90,7 @@ setup(name='%s_%s' % (PREFIX, MODULE), ], package_data={ 'trytond.modules.%s' % MODULE: (info.get('xml', []) - + ['tryton.cfg', 'locale/*.po', 'view/*.xml', 'tests/*.rst']), + + ['tryton.cfg', 'locale/*.po', 'tests/*.rst']), }, classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -71,12 +110,17 @@ setup(name='%s_%s' % (PREFIX, MODULE), 'Natural Language :: Russian', 'Natural Language :: Spanish', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.6', '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', ], license='GPL-3', install_requires=requires, + dependency_links=dependency_links, zip_safe=False, entry_points=""" [trytond.modules] @@ -85,4 +129,9 @@ setup(name='%s_%s' % (PREFIX, MODULE), test_suite='tests', test_loader='trytond.test_loader:Loader', tests_require=tests_require, + use_2to3=True, + convert_2to3_doctests=[ + 'tests/scenario_account_invoice_discount.rst', + 'tests/scenario_account_invoice_information_uom.rst', + ], ) diff --git a/tests/__init__.py b/tests/__init__.py index 063097f..2826ac6 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,10 @@ # The COPYRIGHT file at the top level of this repository contains the full # 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'] diff --git a/tests/scenario_product_cost_plan.rst b/tests/scenario_product_cost_plan.rst index bcc48bd..6f00fc8 100644 --- a/tests/scenario_product_cost_plan.rst +++ b/tests/scenario_product_cost_plan.rst @@ -13,44 +13,22 @@ Imports:: >>> from decimal import Decimal >>> from proteus import config, Model, Wizard >>> today = datetime.date.today() - -Create database:: - - >>> config = config.set_trytond() - >>> config.pool.test = True + >>> from trytond.tests.tools import activate_modules + >>> from trytond.modules.company.tests.tools import create_company, \ + ... get_company Install product_cost_plan Module:: - >>> Module = Model.get('ir.module') - >>> modules = Module.find([('name', '=', 'product_cost_plan')]) - >>> Module.install([x.id for x in modules], config.context) - >>> Wizard('ir.module.install_upgrade').execute('upgrade') + >>> config = activate_modules('product_cost_plan') 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() + >>> _ = create_company() + >>> company = get_company() + >>> tax_identifier = company.party.identifiers.new() + >>> tax_identifier.type = 'eu_vat' + >>> tax_identifier.code = 'BE0897290877' + >>> company.party.save() Reload the context:: @@ -64,15 +42,18 @@ Create product:: >>> ProductTemplate = Model.get('product.template') >>> template = ProductTemplate() >>> template.name = 'product' + >>> template.producible = True >>> template.default_uom = unit >>> template.type = 'goods' >>> template.list_price = Decimal(30) - >>> template.cost_price = Decimal(20) >>> template.save() >>> product, = template.products + >>> product.cost_price = Decimal(20) + >>> product.save() >>> template = ProductTemplate() >>> template.name = 'product 2' + >>> template.producible = True >>> template.default_uom = unit >>> template.type = 'goods' >>> template.list_price = Decimal(30) @@ -82,6 +63,7 @@ Create product:: >>> template = ProductTemplate() >>> template.name = 'product 3' + >>> template.producible = True >>> template.default_uom = unit >>> template.type = 'goods' >>> template.list_price = Decimal(15) @@ -98,36 +80,41 @@ Create Components:: >>> templateA.default_uom = meter >>> templateA.type = 'goods' >>> templateA.list_price = Decimal(2) - >>> templateA.cost_price = Decimal(1) >>> templateA.save() >>> componentA, = templateA.products + >>> componentA.cost_price = Decimal(1) + >>> componentA.save() >>> templateB = ProductTemplate() >>> templateB.name = 'component B' >>> templateB.default_uom = meter >>> templateB.type = 'goods' >>> templateB.list_price = Decimal(2) - >>> templateB.cost_price = Decimal(1) >>> templateB.save() >>> componentB, = templateB.products + >>> componentB.cost_price = Decimal(1) + >>> componentB.save() >>> template1 = ProductTemplate() >>> template1.name = 'component 1' >>> template1.default_uom = unit + >>> template1.producible = True >>> template1.type = 'goods' >>> template1.list_price = Decimal(5) - >>> template1.cost_price = Decimal(2) >>> template1.save() >>> component1, = template1.products + >>> component1.cost_price = Decimal(2) + >>> component1.save() >>> template2 = ProductTemplate() >>> template2.name = 'component 2' >>> template2.default_uom = meter >>> template2.type = 'goods' >>> template2.list_price = Decimal(7) - >>> template2.cost_price = Decimal(5) >>> template2.save() >>> component2, = template2.products + >>> component2.cost_price = Decimal(5) + >>> component2.save() Create Bill of Material:: @@ -145,7 +132,7 @@ Create Bill of Material:: >>> component_bom.save() >>> ProductBom = Model.get('product.product-production.bom') - >>> component1.boms.append(ProductBom(bom=component_bom)) + >>> component1.boms.append(ProductBom (bom=component_bom)) >>> component1.save() >>> bom = BOM(name='product') @@ -169,6 +156,7 @@ Create a cost plan from BoM without child BoMs:: >>> CostPlan = Model.get('product.cost.plan') >>> plan = CostPlan() + >>> plan.number = '1' >>> plan.product = product >>> plan.bom == bom True @@ -201,10 +189,10 @@ Create a cost plan from BoM without child BoMs:: >>> cost, = plan.costs >>> cost.rec_name == 'Raw materials' True - >>> cost.cost == Decimal('17.5') - True >>> plan.cost_price == Decimal('17.5') True + >>> cost.cost == Decimal('17.5') + True Create a manual cost and test total cost is updated:: @@ -344,4 +332,3 @@ Create BoM from Cost Plan:: True >>> plan3.bom.outputs[0].quantity 2.0 - diff --git a/tests/scenario_product_cost_plan_extras_depend.rst b/tests/scenario_product_cost_plan_extras_depend.rst index 68d5922..666b016 100644 --- a/tests/scenario_product_cost_plan_extras_depend.rst +++ b/tests/scenario_product_cost_plan_extras_depend.rst @@ -12,52 +12,25 @@ Imports:: >>> from dateutil.relativedelta import relativedelta >>> from decimal import Decimal >>> 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() -Create database:: - - >>> config = config.set_trytond() - >>> config.pool.test = True Install product_cost_plan Module:: - >>> Module = Model.get('ir.module') - >>> 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') + >>> config = activate_modules(['product_cost_plan', 'production_external_party']) 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() + >>> _ = create_company() + >>> company = get_company() + >>> tax_identifier = company.party.identifiers.new() + >>> tax_identifier.type = 'eu_vat' + >>> tax_identifier.code = 'BE0897290877' + >>> company.party.save() Reload the context:: @@ -74,16 +47,18 @@ Create product:: >>> template.default_uom = unit >>> template.type = 'goods' >>> template.list_price = Decimal(30) - >>> template.cost_price = Decimal(20) + >>> template.producible = True >>> template.save() >>> product, = template.products + >>> product.cost_price = Decimal(20) + >>> product.save() >>> template = ProductTemplate() >>> template.name = 'product 2' >>> template.default_uom = unit >>> template.type = 'goods' >>> template.list_price = Decimal(30) - >>> template.cost_price = Decimal(0) + >>> template.producible = True >>> template.save() >>> product2, = template.products @@ -92,7 +67,7 @@ Create product:: >>> template.default_uom = unit >>> template.type = 'goods' >>> template.list_price = Decimal(15) - >>> template.cost_price = Decimal(0) + >>> template.producible = True >>> template.save() >>> product3, = template.products @@ -105,19 +80,21 @@ Create Components:: >>> template1.default_uom = unit >>> template1.type = 'goods' >>> template1.list_price = Decimal(5) - >>> template1.cost_price = Decimal(2) >>> template1.may_belong_to_party = True >>> template1.save() >>> component1, = template1.products + >>> component1.cost_price = Decimal(2) + >>> component1.save() >>> template2 = ProductTemplate() >>> template2.name = 'component 2' >>> template2.default_uom = meter >>> template2.type = 'goods' >>> template2.list_price = Decimal(7) - >>> template2.cost_price = Decimal(5) >>> template2.save() >>> component2, = template2.products + >>> component2.cost_price = Decimal(5) + >>> component2.save() 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') >>> plan = CostPlan() + >>> plan.number = '1' >>> plan.product = product >>> plan.bom == bom True @@ -181,8 +159,6 @@ Set party stock also for second component:: >>> for product_line in plan2.products: ... if product_line.product == component2: ... product_line.party_stock = True - ... product_line.save() - ... product_line.reload() >>> plan2.save() >>> plan2.reload() >>> sorted([(p.quantity, p.product.rec_name, bool(p.party_stock), p.cost_price) @@ -284,4 +260,3 @@ Create BoM from Cost Plan:: True >>> plan3.bom.outputs[0].quantity 2.0 - diff --git a/tests/test_product_cost_plan.py b/tests/test_product_cost_plan.py index d898639..61640f5 100644 --- a/tests/test_product_cost_plan.py +++ b/tests/test_product_cost_plan.py @@ -5,26 +5,26 @@ import unittest import doctest import trytond.tests.test_tryton 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 -class TestCase(ModuleTestCase): +class ProductCostPlanTestCase(ModuleTestCase): 'Test module' module = 'product_cost_plan' def suite(): suite = trytond.tests.test_tryton.suite() - suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCase)) - suite.addTests(doctest.DocFileSuite( - 'scenario_product_cost_plan.rst', - setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', + suite.addTests(unittest.TestLoader().loadTestsFromTestCase( + ProductCostPlanTestCase)) + suite.addTests(doctest.DocFileSuite('scenario_product_cost_plan.rst', + tearDown=doctest_teardown, encoding='utf-8', checker=doctest_checker, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) - suite.addTests(doctest.DocFileSuite( - 'scenario_product_cost_plan_extras_depend.rst', - setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', + suite.addTests( + doctest.DocFileSuite('scenario_product_cost_plan_extras_depend.rst', + tearDown=doctest_teardown, encoding='utf-8', checker=doctest_checker, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2082ab4 --- /dev/null +++ b/tox.ini @@ -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} diff --git a/tryton.cfg b/tryton.cfg index cb38d02..1a5335b 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,6 @@ [tryton] -version=4.1.0 +version=4.8.0 + depends: production extras_depend: diff --git a/view/cost_plan_bom_line_form.xml b/view/cost_plan_bom_line_form.xml index c5971a8..32ebeac 100644 --- a/view/cost_plan_bom_line_form.xml +++ b/view/cost_plan_bom_line_form.xml @@ -1,7 +1,7 @@ -
+