From 55d15aa597c2a5727b88f5887bb332d0eb22d9f7 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 3 Jun 2019 11:34:51 -0700 Subject: [PATCH 01/36] IMP `connector_walmart` Add the ability to set the Salesperson on imported Walmart orders. --- connector_walmart/__manifest__.py | 2 +- connector_walmart/models/sale_order/importer.py | 5 +++++ connector_walmart/models/walmart_backend/common.py | 2 ++ connector_walmart/views/walmart_backend_views.xml | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/connector_walmart/__manifest__.py b/connector_walmart/__manifest__.py index 33bca3fb..534a6e33 100644 --- a/connector_walmart/__manifest__.py +++ b/connector_walmart/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Walmart Connector', - 'version': '12.0.1.0.0', + 'version': '12.0.1.1.0', 'category': 'Connector', 'depends': [ 'account', diff --git a/connector_walmart/models/sale_order/importer.py b/connector_walmart/models/sale_order/importer.py index adf4c099..f10518d3 100644 --- a/connector_walmart/models/sale_order/importer.py +++ b/connector_walmart/models/sale_order/importer.py @@ -123,6 +123,11 @@ class SaleOrderImportMapper(Component): if self.backend_record.team_id: return {'team_id': self.backend_record.team_id.id} + @mapping + def user_id(self, record): + if self.backend_record.user_id: + return {'user_id': self.backend_record.user_id.id} + @mapping def payment_mode_id(self, record): assert self.backend_record.payment_mode_id, ("Payment mode must be specified.") diff --git a/connector_walmart/models/walmart_backend/common.py b/connector_walmart/models/walmart_backend/common.py index 4f4d60cc..673857f1 100644 --- a/connector_walmart/models/walmart_backend/common.py +++ b/connector_walmart/models/walmart_backend/common.py @@ -59,6 +59,8 @@ class WalmartBackend(models.Model): 'field on the sale order created by the connector.' ) team_id = fields.Many2one(comodel_name='crm.team', string='Sales Team') + user_id = fields.Many2one(comodel_name='res.users', string='Salesperson', + help="Default Salesperson for newly imported orders.") sale_prefix = fields.Char( string='Sale Prefix', help="A prefix put before the name of imported sales orders.\n" diff --git a/connector_walmart/views/walmart_backend_views.xml b/connector_walmart/views/walmart_backend_views.xml index af4c84e6..00814d97 100644 --- a/connector_walmart/views/walmart_backend_views.xml +++ b/connector_walmart/views/walmart_backend_views.xml @@ -30,6 +30,7 @@ + From b9835c3fb6a0d0662fc9537686f3f9795e60cd4c Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 18 Jun 2019 11:21:37 -0700 Subject: [PATCH 02/36] IMP `purchase_by_sale_history` Convert "to buy" QTY to the PO Line UOM. This means that if it is making a new line, it will be the "Purchase UOM", if it is re-using a line it will be the current line UOM (which may have been changed by the user). --- .../wizard/purchase_by_sale_history.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/purchase_by_sale_history/wizard/purchase_by_sale_history.py b/purchase_by_sale_history/wizard/purchase_by_sale_history.py index 2e9c91ae..eb19c316 100644 --- a/purchase_by_sale_history/wizard/purchase_by_sale_history.py +++ b/purchase_by_sale_history/wizard/purchase_by_sale_history.py @@ -79,6 +79,12 @@ class PurchaseBySaleHistory(models.TransientModel): qty = ceil(history['sold_qty'] * self.procure_days / self.history_days) history['buy_qty'] = max((0.0, qty - product.virtual_available)) + def _convert_to_purchase_line_qty(self, line, qty): + # Skip calculation if they are the same UOM + if line.product_id.uom_id != line.product_uom: + return line.product_id.uom_id._compute_quantity(qty, line.product_uom) + return qty + def _apply_history(self, history, product_ids): line_model = self.env['purchase.order.line'] updated_lines = line_model.browse() @@ -98,13 +104,12 @@ class PurchaseBySaleHistory(models.TransientModel): self._apply_history_product(p, history_dict[p.id]) for pid, history in history_dict.items(): - # TODO: Should convert from Sale UOM to Purchase UOM qty = history.get('buy_qty', 0.0) # Find line that already exists on PO line = self.purchase_id.order_line.filtered(lambda l: l.product_id.id == pid) if line: - line.write({'product_qty': qty}) + line.write({'product_qty': self._convert_to_purchase_line_qty(line, qty)}) line._onchange_quantity() else: # Create new PO line @@ -115,7 +120,7 @@ class PurchaseBySaleHistory(models.TransientModel): }) line.onchange_product_id() line_vals = line._convert_to_write(line._cache) - line_vals['product_qty'] = qty + line_vals['product_qty'] = self._convert_to_purchase_line_qty(line, qty) line = line_model.create(line_vals) updated_lines += line From e59cdc0000d521db7273093c9d5e8027ad15888c Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 15 Jul 2019 08:17:42 -0700 Subject: [PATCH 03/36] FIX `sale_planner` FakeSaleOrder project_id => analytic_accout_id --- sale_planner/wizard/order_planner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sale_planner/wizard/order_planner.py b/sale_planner/wizard/order_planner.py index c884fb58..75db1e3b 100644 --- a/sale_planner/wizard/order_planner.py +++ b/sale_planner/wizard/order_planner.py @@ -132,7 +132,7 @@ class FakeSaleOrder(): self.id = 0 self.name = 'Quote' self.team_id = None - self.project_id = None + self.analytic_account_id = None self.amount_total = 0.0 self.date_order = fields.Date.today() self.shipping_account_id = False # from delivery_hibou From db7e47ac7d82878e3ca1e619b4c62e0e304188ec Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 15 Jul 2019 09:47:51 -0700 Subject: [PATCH 04/36] IMP `sale_planner` Fake objects now return False for any attribute they don't implement. --- sale_planner/wizard/order_planner.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sale_planner/wizard/order_planner.py b/sale_planner/wizard/order_planner.py index 75db1e3b..09b69169 100644 --- a/sale_planner/wizard/order_planner.py +++ b/sale_planner/wizard/order_planner.py @@ -78,6 +78,9 @@ class FakePartner(): return self.date_localization + def __getattr__(self, item): + return False + class FakeOrderLine(): def __init__(self, **kwargs): @@ -108,6 +111,9 @@ class FakeOrderLine(): """ return qty + def __getattr__(self, item): + return False + class FakeSaleOrder(): """ @@ -149,6 +155,10 @@ class FakeSaleOrder(): def _compute_amount_total_without_delivery(self): return self.amount_total + def __getattr__(self, item): + return False + + def distance(lat_1, lon_1, lat_2, lon_2): R = 6373.0 From 77e09d5de92fb3ca657f298526a7c8c248229a2a Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 15 Jul 2019 12:29:38 -0700 Subject: [PATCH 05/36] IMP `order_planner` allow subscription access to Fakes --- sale_planner/wizard/order_planner.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sale_planner/wizard/order_planner.py b/sale_planner/wizard/order_planner.py index 09b69169..c23bf8f8 100644 --- a/sale_planner/wizard/order_planner.py +++ b/sale_planner/wizard/order_planner.py @@ -81,6 +81,11 @@ class FakePartner(): def __getattr__(self, item): return False + def __getitem__(self, item): + if item == '__last_update': + return str(datetime.now()) + return getattr(self, item) + class FakeOrderLine(): def __init__(self, **kwargs): @@ -114,6 +119,11 @@ class FakeOrderLine(): def __getattr__(self, item): return False + def __getitem__(self, item): + if item == '__last_update': + return str(datetime.now()) + return getattr(self, item) + class FakeSaleOrder(): """ @@ -158,6 +168,11 @@ class FakeSaleOrder(): def __getattr__(self, item): return False + def __getitem__(self, item): + if item == '__last_update': + return str(datetime.now()) + return getattr(self, item) + def distance(lat_1, lon_1, lat_2, lon_2): R = 6373.0 From 6ed97d0a391c309e7f63ce230cbd0730d693ccba Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 16 Jul 2019 15:07:41 -0700 Subject: [PATCH 06/36] IMP `sale_planner` FakePartner now provides a FakePartner parent_id --- sale_planner/wizard/order_planner.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sale_planner/wizard/order_planner.py b/sale_planner/wizard/order_planner.py index c23bf8f8..6e27fcff 100644 --- a/sale_planner/wizard/order_planner.py +++ b/sale_planner/wizard/order_planner.py @@ -50,6 +50,9 @@ class FakePartner(): self.partner_latitude = 0.0 self.partner_longitude = 0.0 self.is_company = False + self._date_localization = kwargs.pop('date_localization', False) + if not kwargs.pop('PARENT', False): + self.parent_id = FakePartner(PARENT=True) for attr, value in kwargs.items(): setattr(self, attr, value) From 5e3655ffaaab6a1d824ed760f7ad0cf60654790d Mon Sep 17 00:00:00 2001 From: David Frick Date: Thu, 11 Jul 2019 15:49:43 -0400 Subject: [PATCH 07/36] Port commit '6904f71fa4bc241dcd3d0f8e973256848e6922ab' into 12.0-test --- l10n_us_nc_hr_payroll/data/rules.xml | 2 +- l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py | 4 ++-- l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py | 10 +++------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/l10n_us_nc_hr_payroll/data/rules.xml b/l10n_us_nc_hr_payroll/data/rules.xml index 8d06ea80..6d404cd0 100755 --- a/l10n_us_nc_hr_payroll/data/rules.xml +++ b/l10n_us_nc_hr_payroll/data/rules.xml @@ -14,7 +14,7 @@ rate = payslip.dict.get_rate('US_NC_UNEMP') year = payslip.dict.date_to.year -ytd = payslip.sum('NC_UNEMP_WAGES_2018', str(year) + '-01-01', str(year+1) + '-01-01') +ytd = payslip.sum('WAGE_US_NC_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01') ytd += contract.external_wages remaining = rate.wage_limit_year - ytd if remaining <= 0.0: diff --git a/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py index eb5079fe..b5ae614b 100755 --- a/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py +++ b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2018.py @@ -248,13 +248,13 @@ class TestUsNCPayslip(TestUsPayslip): salary = 4000.0 wh = 0 schedule_pay = 'weekly' - excemptions = 1 + exemptions = 1 employee = self._createEmployee() contract = self._createContract(employee, salary, struct_id=self.ref( 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay) - contract.nc_nc4_allowances = excemptions + contract.nc_nc4_allowances = exemptions contract.nc_nc4_filing_status = 'exempt' self.assertEqual(contract.schedule_pay, 'weekly') diff --git a/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py index 2b30e8c1..79599fb1 100755 --- a/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py +++ b/l10n_us_nc_hr_payroll/tests/test_us_nc_payslip_2019.py @@ -10,9 +10,8 @@ class TestUsNCPayslip(TestUsPayslip): NC_UNEMP = -1.0 / 100.0 NC_INC_TAX = -0.0535 - def test_2019_taxes_weekly(self): - salary = 5000.0 + salary = 20000.0 schedule_pay = 'weekly' # allowance_multiplier and Portion of Standard Deduction for weekly allowance_multiplier = 48.08 @@ -20,11 +19,9 @@ class TestUsNCPayslip(TestUsPayslip): exemption = 1 # Algorithm derived from percentage method in https://files.nc.gov/ncdor/documents/files/nc-30_book_web.pdf - wh = -round((salary - (PST + (allowance_multiplier * exemption))) * -self.NC_INC_TAX) employee = self._createEmployee() - contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay) contract.nc_nc4_allowances = exemption @@ -47,7 +44,6 @@ class TestUsNCPayslip(TestUsPayslip): remaining_NC_UNEMP_wages = self.NC_UNEMP_MAX_WAGE - salary if (self.NC_UNEMP_MAX_WAGE - 2*salary < salary) \ else salary - self._log('2019 North Carolina tax second payslip weekly:') payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') @@ -249,13 +245,13 @@ class TestUsNCPayslip(TestUsPayslip): salary = 4000.0 wh = 0 schedule_pay = 'weekly' - excemptions = 1 + exemptions = 1 employee = self._createEmployee() contract = self._createContract(employee, salary, struct_id=self.ref( 'l10n_us_nc_hr_payroll.hr_payroll_salary_structure_us_nc_employee'), schedule_pay=schedule_pay) - contract.nc_nc4_allowances = excemptions + contract.nc_nc4_allowances = exemptions contract.nc_nc4_filing_status = 'exempt' self.assertEqual(contract.schedule_pay, 'weekly') From 97c9c30d52d22abe1a510a62278be19ee7961c14 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 25 Jul 2019 15:14:33 -0700 Subject: [PATCH 08/36] IMP `delivery_partner_dhl` Allow 8-10 digit account numbers. --- delivery_partner_dhl/models/delivery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/delivery_partner_dhl/models/delivery.py b/delivery_partner_dhl/models/delivery.py index db975f51..6de6583a 100644 --- a/delivery_partner_dhl/models/delivery.py +++ b/delivery_partner_dhl/models/delivery.py @@ -10,8 +10,8 @@ class PartnerShippingAccount(models.Model): delivery_type = fields.Selection(selection_add=[('dhl', 'DHL')]) def dhl_check_validity(self): - m = re.search(r'^\d{10}$', self.name or '') + m = re.search(r'^(\d{8}|\d{9}|\d{10})$', self.name or '') if not m: - raise ValidationError('DHL Account numbers must be 10 decimal numbers.') + raise ValidationError('DHL Account numbers must be 8-10 decimal numbers.') From 055032f7d4de6ca481a06e3d735ce40150d49432 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 6 Aug 2019 05:54:34 -0700 Subject: [PATCH 09/36] NEW Addition to gitlab-ci for creating merge reqeusts --- .gitlab-ci-automerge.sh | 58 +++++++++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 10 +++++++ 2 files changed, 68 insertions(+) create mode 100755 .gitlab-ci-automerge.sh diff --git a/.gitlab-ci-automerge.sh b/.gitlab-ci-automerge.sh new file mode 100755 index 00000000..eda4db46 --- /dev/null +++ b/.gitlab-ci-automerge.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -e +set -x + +echo "Processing for merge requests." + +HOST="${HOST}api/v4/projects/" + +TARGET_BRANCH=$RELEASE + +BODY="{ + \"id\": ${CI_PROJECT_ID}, + \"source_branch\": \"${CI_COMMIT_REF_NAME}\", + \"target_branch\": \"${TARGET_BRANCH}\", + \"remove_source_branch\": true, + \"title\": \"WIP RELEASE: ${CI_COMMIT_REF_NAME}\", + \"assignee_id\":\"${GITLAB_USER_ID}\" +}"; + +LISTMR=`curl --silent "${HOST}${CI_PROJECT_ID}/merge_requests?state=opened" --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}"`; +MATCHES=`echo ${LISTMR} | jq ".[] | {target_branch: .target_branch | match(\"${TARGET_BRANCH}\"), source_branch: .source_branch | match(\"${CI_COMMIT_REF_NAME}\")}"`; + +# No MR found, let's create a new one +if [ -z "${MATCHES}" ]; then + curl -X POST "${HOST}${CI_PROJECT_ID}/merge_requests" \ + --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}" \ + --header "Content-Type: application/json" \ + --data "${BODY}"; + + echo "Opened a new merge request: WIP RELEASE: ${CI_COMMIT_REF_NAME} and assigned to you"; +fi + +# Test +TARGET_BRANCH="${RELEASE}-test" + +BODY="{ + \"id\": ${CI_PROJECT_ID}, + \"source_branch\": \"${CI_COMMIT_REF_NAME}\", + \"target_branch\": \"${TARGET_BRANCH}\", + \"remove_source_branch\": false, + \"title\": \"WIP TEST: ${CI_COMMIT_REF_NAME}\", + \"assignee_id\":\"${GITLAB_USER_ID}\" +}"; + +LISTMR=`curl --silent "${HOST}${CI_PROJECT_ID}/merge_requests?state=opened" --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}"`; +MATCHES=`echo ${LISTMR} | jq ".[] | {target_branch: .target_branch | match(\"${TARGET_BRANCH}\"), source_branch: .source_branch | match(\"${CI_COMMIT_REF_NAME}\")}"`; + +# No MR found, let's create a new one +if [ -z "${MATCHES}" ]; then + curl -X POST "${HOST}${CI_PROJECT_ID}/merge_requests" \ + --header "PRIVATE-TOKEN:${PRIVATE_TOKEN}" \ + --header "Content-Type: application/json" \ + --data "${BODY}"; + + echo "Opened a new merge request: WIP TEST: ${CI_COMMIT_REF_NAME} and assigned to you"; +fi + + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97144120..3b65e88e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,6 +21,16 @@ before_script: - git submodule sync --recursive - git submodule update --init --recursive +merge_request: + before_script: + - apk add curl jq + stage: build + only: + - /(^new\/)|(^mig\/)|(^imp\/)|(^fix\/)/ + script: + - ls -lah + - RELEASE=$(echo $CI_COMMIT_REF_NAME | sed "s{.*\/\(.*\)\/.*{\1{g") HOST=$(echo $CI_PROJECT_URL | sed 's{\(^https://[^/]*/\).*{\1{g') CI_PROJECT_ID=${CI_PROJECT_ID} CI_COMMIT_REF_NAME=${CI_COMMIT_REF_NAME} GITLAB_USER_ID=${GITLAB_USER_ID} PRIVATE_TOKEN=${PRIVATE_TOKEN} ./.gitlab-ci-automerge.sh + build: stage: build script: From 08af152344ac8e406d255b1b8398c25fd0afd49d Mon Sep 17 00:00:00 2001 From: David Frick Date: Wed, 10 Jul 2019 18:50:38 -0400 Subject: [PATCH 10/36] NEW `l10n_us_ar_hr_payroll` for 11.0 --- l10n_us_ar_hr_payroll/__init__.py | 1 + l10n_us_ar_hr_payroll/__manifest__.py | 28 ++ l10n_us_ar_hr_payroll/data/base.xml | 47 +++ l10n_us_ar_hr_payroll/data/final.xml | 17 + l10n_us_ar_hr_payroll/data/rates.xml | 14 + l10n_us_ar_hr_payroll/data/rules.xml | 115 +++++++ l10n_us_ar_hr_payroll/models/__init__.py | 1 + l10n_us_ar_hr_payroll/models/hr_payroll.py | 10 + l10n_us_ar_hr_payroll/tests/__init__.py | 1 + .../tests/test_us_ar_payslip_2019.py | 292 ++++++++++++++++++ .../views/hr_payroll_views.xml | 23 ++ 11 files changed, 549 insertions(+) create mode 100755 l10n_us_ar_hr_payroll/__init__.py create mode 100755 l10n_us_ar_hr_payroll/__manifest__.py create mode 100755 l10n_us_ar_hr_payroll/data/base.xml create mode 100755 l10n_us_ar_hr_payroll/data/final.xml create mode 100755 l10n_us_ar_hr_payroll/data/rates.xml create mode 100755 l10n_us_ar_hr_payroll/data/rules.xml create mode 100644 l10n_us_ar_hr_payroll/models/__init__.py create mode 100755 l10n_us_ar_hr_payroll/models/hr_payroll.py create mode 100755 l10n_us_ar_hr_payroll/tests/__init__.py create mode 100755 l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py create mode 100755 l10n_us_ar_hr_payroll/views/hr_payroll_views.xml diff --git a/l10n_us_ar_hr_payroll/__init__.py b/l10n_us_ar_hr_payroll/__init__.py new file mode 100755 index 00000000..0650744f --- /dev/null +++ b/l10n_us_ar_hr_payroll/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_us_ar_hr_payroll/__manifest__.py b/l10n_us_ar_hr_payroll/__manifest__.py new file mode 100755 index 00000000..1bb42dad --- /dev/null +++ b/l10n_us_ar_hr_payroll/__manifest__.py @@ -0,0 +1,28 @@ +{ + 'name': 'USA - Arkansas - Payroll', + 'author': 'Hibou Corp. ', + 'license': 'AGPL-3', + 'category': 'Localization', + 'depends': ['l10n_us_hr_payroll'], + 'version': '11.0.2019.0.0', + 'description': """ +USA::Arkansas Payroll Rules. +================================== + +* Contribution register and partner for Arkansas Department of Financial Administration - Income Tax Withholding +* Contribution register and partner for Arkansas Department of Workforce Solutions - Unemployment +* Contract level Arkansas Exemptions +* Company level Arkansas Unemployment Rate +* Salary Structure for Arkansas + """, + 'auto_install': False, + 'website': 'https://hibou.io/', + 'data': [ + 'views/hr_payroll_views.xml', + 'data/base.xml', + 'data/rates.xml', + 'data/rules.xml', + 'data/final.xml', + ], + 'installable': True +} diff --git a/l10n_us_ar_hr_payroll/data/base.xml b/l10n_us_ar_hr_payroll/data/base.xml new file mode 100755 index 00000000..38534fdd --- /dev/null +++ b/l10n_us_ar_hr_payroll/data/base.xml @@ -0,0 +1,47 @@ + + + + + + Arkansas Department of Workforce Solutions - Unemployment Tax + 1 + + + + Arkansas Department of Financial Administration- Income Tax Withholding + 1 + + + + + + Arkansas Unemployment + Arkansas Department of Workforce Solutions - Unemployment + + + + Arkansas Income Tax Withholding + Arkansas Department of Financial Administration - Income Tax Withholding + + + + + + + Wage: US-AR Unemployment + WAGE_US_AR_UNEMP + + + + ER: US-AR Unemployment + ER_US_AR_UNEMP + + + + + EE: US-AR Income Tax Withholding + EE_US_AR_INC_WITHHOLD + + + + diff --git a/l10n_us_ar_hr_payroll/data/final.xml b/l10n_us_ar_hr_payroll/data/final.xml new file mode 100755 index 00000000..6bbf8f32 --- /dev/null +++ b/l10n_us_ar_hr_payroll/data/final.xml @@ -0,0 +1,17 @@ + + + + + + US_AR_EMP + USA Arkansas Employee + + + + + + diff --git a/l10n_us_ar_hr_payroll/data/rates.xml b/l10n_us_ar_hr_payroll/data/rates.xml new file mode 100755 index 00000000..00c3b202 --- /dev/null +++ b/l10n_us_ar_hr_payroll/data/rates.xml @@ -0,0 +1,14 @@ + + + + + + US AR Unemployment + US_AR_UNEMP + 3.2 + 2019-01-01 + + + + + diff --git a/l10n_us_ar_hr_payroll/data/rules.xml b/l10n_us_ar_hr_payroll/data/rules.xml new file mode 100755 index 00000000..7082c131 --- /dev/null +++ b/l10n_us_ar_hr_payroll/data/rules.xml @@ -0,0 +1,115 @@ + + + + + + + Wage: US-AR Unemployment + WAGE_US_AR_UNEMP + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC and not contract.ar_w4_tax_exempt and not contract.ar_w4_texarkana_exemption) + code + +rate = payslip.dict.get_rate('US_AR_UNEMP') +year = int(payslip.dict.date_to[:4]) +ytd = payslip.sum('WAGE_US_AR_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01') +ytd += contract.external_wages +remaining = rate.wage_limit_year - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.BASIC: + result = remaining +else: + result = categories.BASIC + + + + + + + + ER: US-AR Unemployment + ER_US_AR_UNEMP + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC and not contract.ar_w4_tax_exempt and not contract.ar_w4_texarkana_exemption) + code + +rate = payslip.dict.get_rate('US_AR_UNEMP') +result_rate = -rate.rate +result = categories.WAGE_US_AR_UNEMP + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + EE: US-AR Income Tax Withholding + EE_US_AR_INC_WITHHOLD + python + result = not (contract.ar_w4_texarkana_exemption or contract.ar_w4_tax_exempt) + code + +wages = categories.GROSS +annual_gross_pay = 0.00 +allowance_amt = contract.ar_w4_allowances * 26.00 +schedule_pay = contract.schedule_pay +standard_deduction = 2200 +pay_period = 0.0 +additional_withholding = contract.ar_w4_additional_wh + +if contract.w4_filing_status == 'married': + standard_deduction = standard_deduction * 2 + +if schedule_pay == 'daily': + pay_period = 260.0 +elif schedule_pay == 'weekly': + pay_period = 52.0 +elif schedule_pay == 'bi-weekly': + pay_period = 26.0 +elif schedule_pay == 'semi-monthly': + pay_period = 24.0 +elif schedule_pay == 'monthly': + pay_period = 12.0 +else: + raise Exception('Invalid schedule_pay="' + schedule_pay + '" for AR Income Withholding calculation') + +annual_gross_pay = (wages * pay_period) +net_taxable_income = annual_gross_pay - standard_deduction - allowance_amt +if (net_taxable_income < 50000.00): + # This formula will round the number to the nearest 50 if under 50000 + net_taxable_income = (net_taxable_income // 50) * 50.0 + 50.0 + +tax_rate_table = [(4299, 0.90), + (8499, 2.50), + (12699, 3.50), + (21199, 4.50), + (35099, 6.0), + (float('inf'), 6.9)] + +result = 0.0 +last = 0.0 + +for row in tax_rate_table: + cap, rate = row + if cap <= net_taxable_income: + taxed = cap - last + result = result + (taxed * (rate / 100.0)) + last = cap + elif cap > net_taxable_income: + taxed = net_taxable_income - last + result = result + (taxed * (rate / 100.0)) + break + +result = (result / pay_period) + additional_withholding +result = -result + + + + + diff --git a/l10n_us_ar_hr_payroll/models/__init__.py b/l10n_us_ar_hr_payroll/models/__init__.py new file mode 100644 index 00000000..e99aa24a --- /dev/null +++ b/l10n_us_ar_hr_payroll/models/__init__.py @@ -0,0 +1 @@ +from . import hr_payroll diff --git a/l10n_us_ar_hr_payroll/models/hr_payroll.py b/l10n_us_ar_hr_payroll/models/hr_payroll.py new file mode 100755 index 00000000..b6b51866 --- /dev/null +++ b/l10n_us_ar_hr_payroll/models/hr_payroll.py @@ -0,0 +1,10 @@ +from odoo import models, fields, api + + +class USARHrContract(models.Model): + _inherit = 'hr.contract' + + ar_w4_allowances = fields.Integer(string='Arkansas W-4 allowances', default=0) + ar_w4_additional_wh = fields.Float(string="Arkansas Additional Withholding", default=0.0) + ar_w4_tax_exempt = fields.Boolean(string="Tax Exempt") + ar_w4_texarkana_exemption = fields.Boolean(string="Texarkana Exemption") diff --git a/l10n_us_ar_hr_payroll/tests/__init__.py b/l10n_us_ar_hr_payroll/tests/__init__.py new file mode 100755 index 00000000..7d2ca3e4 --- /dev/null +++ b/l10n_us_ar_hr_payroll/tests/__init__.py @@ -0,0 +1 @@ +from . import test_us_ar_payslip_2019 diff --git a/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py b/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py new file mode 100755 index 00000000..0468f48f --- /dev/null +++ b/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py @@ -0,0 +1,292 @@ +from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip +from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract + + +class TestUsARPayslip(TestUsPayslip): + + AR_UNEMP_MAX_WAGE = 10000.00 + AR_UNEMP = -3.2 / 100.0 + AR_INC_TAX = -0.0535 + + def test_taxes_monthly(self): + salary = 10000.0 + schedule_pay = 'monthly' + + employee = self._createEmployee() + contract = self._createContract(employee, salary, + struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + schedule_pay=schedule_pay) + + self.assertEqual(contract.schedule_pay, 'monthly') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - salary if (self.AR_UNEMP_MAX_WAGE - 2*salary < salary) \ + else salary + + self._log('2019 Arkansas tax second payslip weekly:') + payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_AR_UNEMP_wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + + def test_taxes_with_state_exempt(self): + salary = 50000.0 + external_wages = 10000.0 + tax_exempt = True # State withholding should be zero. + + employee = self._createEmployee() + contract = self._createContract(employee, + salary, + external_wages=external_wages, + struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + futa_type=USHrContract.FUTA_TYPE_BASIC) + contract.ar_w4_tax_exempt = tax_exempt + self._log('2019 Arkansas exempt tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats.get('WAGE_US_AR_UNEMP', 0.0), 0.0) + self.assertPayrollEqual(cats.get('ER_US_AR_UNEMP', 0.0), cats.get('WAGE_US_AR_UNEMP', 0.0) * self.AR_UNEMP) + self.assertPayrollEqual(cats.get('EE_US_AR_INC_WITHHOLD', 0.0), 0.0) + + process_payslip(payslip) + + def test_taxes_with_texarkana_exempt(self): + salary = 40000.00 + texarkana_exemption = True # State withholding should be zero. + + employee = self._createEmployee() + contract = self._createContract(employee, + salary, + struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee')) + contract.ar_w4_texarkana_exemption = texarkana_exemption + + + self._log('2019 Arkansas tax first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats.get('WAGE_US_AR_UNEMP', 0.0), 0.0) + self.assertPayrollEqual(cats.get('ER_US_AR_UNEMP', 0.0), cats.get('WAGE_US_AR_UNEMP', 0.0) * self.AR_UNEMP) + + process_payslip(payslip) + + def test_additional_withholding(self): + wages = 5000.0 + schedule_pay = 'monthly' + pay_periods = 12 + additional_wh = 150.0 + exemptions = 2 + # TODO: comment on how it was calculated + test_ar_amt = 3069.97 + + employee = self._createEmployee() + contract = self._createContract(employee, + wages, + struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + schedule_pay=schedule_pay) + contract.ar_w4_additional_wh = 0.0 + contract.ar_w4_allowances = exemptions + + self.assertEqual(contract.schedule_pay, 'monthly') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) + # TODO: change to hand the test_ar_amt already be divided by pay periods + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) + + contract.ar_w4_additional_wh = additional_wh + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) + + process_payslip(payslip) + + def test_under_fifty_thousand(self): + wages = 2500.00 + schedule_pay = 'monthly' + pay_periods = 12 + additional_wh = 150.0 + exemptions = 2 + # TODO: comment calc. + test_ar_amt = 1066.151 + + employee = self._createEmployee() + contract = self._createContract(employee, + wages, + struct_id=self.ref( + 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + schedule_pay=schedule_pay) + contract.ar_w4_additional_wh = 0.0 + contract.ar_w4_allowances = exemptions + + self.assertEqual(contract.schedule_pay, 'monthly') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) + + contract.ar_w4_additional_wh = additional_wh + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - wages if (self.AR_UNEMP_MAX_WAGE - 2 * wages < wages) \ + else wages + + self._log('2019 Arkansas tax second payslip weekly:') + payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_AR_UNEMP_wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + + def test_over_fifty_thousand(self): + wages = 10000.00 + schedule_pay = 'monthly' + pay_periods = 12 + additional_wh = 150.0 + exemptions = 2 + # TODO: comment on how it was calculated + test_ar_amt = 7209.97 + + employee = self._createEmployee() + contract = self._createContract(employee, + wages, + struct_id=self.ref( + 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + schedule_pay=schedule_pay) + contract.ar_w4_additional_wh = 0.0 + contract.ar_w4_allowances = exemptions + + self.assertEqual(contract.schedule_pay, 'monthly') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) + + contract.ar_w4_additional_wh = additional_wh + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - wages if (self.AR_UNEMP_MAX_WAGE - 2 * wages < wages) \ + else wages + + self._log('2019 Arkansas tax second payslip weekly:') + payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_AR_UNEMP_wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + + def test_married(self): + wages = 5500.00 + schedule_pay = 'monthly' + pay_periods = 12 + additional_wh = 150.0 + exemptions = 2 + w4_filing_status = 'married' + # TODO: explain calc. + # Yearly -> 3332.17. Monthly -> 427.681 + test_ar_amt = 3332.17 + + employee = self._createEmployee() + contract = self._createContract(employee, + wages, + struct_id=self.ref( + 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + schedule_pay=schedule_pay) + contract.ar_w4_additional_wh = additional_wh + contract.ar_w4_allowances = exemptions + contract.w4_filing_status = w4_filing_status + + self.assertEqual(contract.w4_filing_status, 'married') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) + + def test_single(self): + wages = 5500.00 + schedule_pay = 'monthly' + pay_periods = 12 + additional_wh = 150.0 + exemptions = 2 + w4_filling_status = 'single' + # TODO: explain calc. + # Yearly -> 3483.972 Monthly -> 298.331 + test_ar_amt = 3483.972 + + employee = self._createEmployee() + contract = self._createContract(employee, + wages, + struct_id=self.ref( + 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), + schedule_pay=schedule_pay) + contract.ar_w4_additional_wh = 0 + contract.ar_w4_allowances = exemptions + contract.w4_filling_status = w4_filling_status + + self.assertEqual(contract.w4_filling_status, 'single') + + self._log('2019 Arkansas tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) + + contract.ar_w4_additional_wh = additional_wh + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) + + process_payslip(payslip) diff --git a/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml b/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml new file mode 100755 index 00000000..d1ed5d9c --- /dev/null +++ b/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml @@ -0,0 +1,23 @@ + + + + + hr.contract.form.inherit + hr.contract + 106 + + + + + + + + + + + + + + + + From 95720aa9c882ffad75431783ac4f144467b6e7c5 Mon Sep 17 00:00:00 2001 From: David Frick Date: Thu, 11 Jul 2019 13:24:52 -0400 Subject: [PATCH 11/36] IMP `l10n_us_ar_hr_payroll` changed pay_period to be dict and modified contribution and partner names to be more readable with state name. --- l10n_us_ar_hr_payroll/data/base.xml | 12 ++++++------ l10n_us_ar_hr_payroll/data/rules.xml | 24 +++++++++++------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/l10n_us_ar_hr_payroll/data/base.xml b/l10n_us_ar_hr_payroll/data/base.xml index 38534fdd..2e004924 100755 --- a/l10n_us_ar_hr_payroll/data/base.xml +++ b/l10n_us_ar_hr_payroll/data/base.xml @@ -2,27 +2,27 @@ - + Arkansas Department of Workforce Solutions - Unemployment Tax 1 - + Arkansas Department of Financial Administration- Income Tax Withholding 1 - + Arkansas Unemployment Arkansas Department of Workforce Solutions - Unemployment - + - + Arkansas Income Tax Withholding Arkansas Department of Financial Administration - Income Tax Withholding - + diff --git a/l10n_us_ar_hr_payroll/data/rules.xml b/l10n_us_ar_hr_payroll/data/rules.xml index 7082c131..b9b73e35 100755 --- a/l10n_us_ar_hr_payroll/data/rules.xml +++ b/l10n_us_ar_hr_payroll/data/rules.xml @@ -42,7 +42,7 @@ result = categories.WAGE_US_AR_UNEMP if result_rate == 0.0: result = 0.0 - + @@ -60,22 +60,20 @@ annual_gross_pay = 0.00 allowance_amt = contract.ar_w4_allowances * 26.00 schedule_pay = contract.schedule_pay standard_deduction = 2200 -pay_period = 0.0 additional_withholding = contract.ar_w4_additional_wh if contract.w4_filing_status == 'married': standard_deduction = standard_deduction * 2 -if schedule_pay == 'daily': - pay_period = 260.0 -elif schedule_pay == 'weekly': - pay_period = 52.0 -elif schedule_pay == 'bi-weekly': - pay_period = 26.0 -elif schedule_pay == 'semi-monthly': - pay_period = 24.0 -elif schedule_pay == 'monthly': - pay_period = 12.0 +pay_period = 0.0 +pay_periods = { + 'weekly': 52.0, + 'bi-weekly': 26.0, + 'semi-monthly': 24.0, + 'monthly': 12.0 + } +if schedule_pay in pay_periods: + pay_period = pay_periods[schedule_pay] else: raise Exception('Invalid schedule_pay="' + schedule_pay + '" for AR Income Withholding calculation') @@ -109,7 +107,7 @@ for row in tax_rate_table: result = (result / pay_period) + additional_withholding result = -result - + From be5d75c18f64b858afe2f8051c367177b100e295 Mon Sep 17 00:00:00 2001 From: David Frick Date: Wed, 17 Jul 2019 10:50:00 -0400 Subject: [PATCH 12/36] FIX `l10n_us_ar_hr_payroll` Arkansas Tax Exempt should not exclude SUTA --- l10n_us_ar_hr_payroll/data/rules.xml | 4 ++-- .../tests/test_us_ar_payslip_2019.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/l10n_us_ar_hr_payroll/data/rules.xml b/l10n_us_ar_hr_payroll/data/rules.xml index b9b73e35..00fed6f5 100755 --- a/l10n_us_ar_hr_payroll/data/rules.xml +++ b/l10n_us_ar_hr_payroll/data/rules.xml @@ -7,7 +7,7 @@ Wage: US-AR Unemployment WAGE_US_AR_UNEMP python - result = (contract.futa_type != contract.FUTA_TYPE_BASIC and not contract.ar_w4_tax_exempt and not contract.ar_w4_texarkana_exemption) + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) code rate = payslip.dict.get_rate('US_AR_UNEMP') @@ -31,7 +31,7 @@ else: ER: US-AR Unemployment ER_US_AR_UNEMP python - result = (contract.futa_type != contract.FUTA_TYPE_BASIC and not contract.ar_w4_tax_exempt and not contract.ar_w4_texarkana_exemption) + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) code rate = payslip.dict.get_rate('US_AR_UNEMP') diff --git a/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py b/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py index 0468f48f..44a4a058 100755 --- a/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py +++ b/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py @@ -24,6 +24,7 @@ class TestUsARPayslip(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) + # Not exempt from rule 1 or rule 2 - unemployment wages., and actual unemployment. self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], salary) self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) @@ -32,7 +33,8 @@ class TestUsARPayslip(TestUsPayslip): # Make a new payslip, this one will have maximums remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - salary if (self.AR_UNEMP_MAX_WAGE - 2*salary < salary) \ else salary - + # We reached the cap of 10000.0 in the first payslip. + self.assertEqual(0.0, remaining_AR_UNEMP_wages) self._log('2019 Arkansas tax second payslip weekly:') payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') payslip.compute_sheet() @@ -43,22 +45,21 @@ class TestUsARPayslip(TestUsPayslip): def test_taxes_with_state_exempt(self): salary = 50000.0 - external_wages = 10000.0 tax_exempt = True # State withholding should be zero. employee = self._createEmployee() contract = self._createContract(employee, salary, - external_wages=external_wages, struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), - futa_type=USHrContract.FUTA_TYPE_BASIC) + ) contract.ar_w4_tax_exempt = tax_exempt + self._log('2019 Arkansas exempt tax first payslip weekly:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats.get('WAGE_US_AR_UNEMP', 0.0), 0.0) + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], self.AR_UNEMP_MAX_WAGE) self.assertPayrollEqual(cats.get('ER_US_AR_UNEMP', 0.0), cats.get('WAGE_US_AR_UNEMP', 0.0) * self.AR_UNEMP) self.assertPayrollEqual(cats.get('EE_US_AR_INC_WITHHOLD', 0.0), 0.0) @@ -74,13 +75,12 @@ class TestUsARPayslip(TestUsPayslip): struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee')) contract.ar_w4_texarkana_exemption = texarkana_exemption - self._log('2019 Arkansas tax first payslip:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats.get('WAGE_US_AR_UNEMP', 0.0), 0.0) + self.assertPayrollEqual(cats.get('WAGE_US_AR_UNEMP', 0.0), self.AR_UNEMP_MAX_WAGE) self.assertPayrollEqual(cats.get('ER_US_AR_UNEMP', 0.0), cats.get('WAGE_US_AR_UNEMP', 0.0) * self.AR_UNEMP) process_payslip(payslip) @@ -172,7 +172,7 @@ class TestUsARPayslip(TestUsPayslip): self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) def test_over_fifty_thousand(self): - wages = 10000.00 + wages = 10000.00 # 10000.00 monthly is over 50,000 annually. schedule_pay = 'monthly' pay_periods = 12 additional_wh = 150.0 From 6d2d6f4f469f4542b3466b94d85fae9adcc8a327 Mon Sep 17 00:00:00 2001 From: David Frick Date: Wed, 14 Aug 2019 16:19:53 -0400 Subject: [PATCH 13/36] MIG `l10n_us_ar_hr_payroll` for 12.0. Changed variables names to match Arkansas Tax Form. Added abbreviations for partners in manifest. --- l10n_us_ar_hr_payroll/__manifest__.py | 8 +-- l10n_us_ar_hr_payroll/data/base.xml | 6 +-- l10n_us_ar_hr_payroll/data/rules.xml | 8 +-- l10n_us_ar_hr_payroll/models/hr_payroll.py | 8 +-- .../tests/test_us_ar_payslip_2019.py | 53 +++++++++---------- .../views/hr_payroll_views.xml | 8 +-- 6 files changed, 45 insertions(+), 46 deletions(-) diff --git a/l10n_us_ar_hr_payroll/__manifest__.py b/l10n_us_ar_hr_payroll/__manifest__.py index 1bb42dad..45f86c6b 100755 --- a/l10n_us_ar_hr_payroll/__manifest__.py +++ b/l10n_us_ar_hr_payroll/__manifest__.py @@ -4,13 +4,13 @@ 'license': 'AGPL-3', 'category': 'Localization', 'depends': ['l10n_us_hr_payroll'], - 'version': '11.0.2019.0.0', + 'version': '12.0.2019.0.0', 'description': """ -USA::Arkansas Payroll Rules. +USA - Arkansas Payroll Rules. ================================== -* Contribution register and partner for Arkansas Department of Financial Administration - Income Tax Withholding -* Contribution register and partner for Arkansas Department of Workforce Solutions - Unemployment +* Contribution register and partner for Arkansas Department of Financial Administration (ADFA) - Income Tax Withholding +* Contribution register and partner for Arkansas Department of Workforce Solutions (ADWS) - Unemployment * Contract level Arkansas Exemptions * Company level Arkansas Unemployment Rate * Salary Structure for Arkansas diff --git a/l10n_us_ar_hr_payroll/data/base.xml b/l10n_us_ar_hr_payroll/data/base.xml index 2e004924..ce1b73cc 100755 --- a/l10n_us_ar_hr_payroll/data/base.xml +++ b/l10n_us_ar_hr_payroll/data/base.xml @@ -3,12 +3,12 @@ - Arkansas Department of Workforce Solutions - Unemployment Tax + Arkansas Department of Workforce Solutions - Unemployment Tax 1 - Arkansas Department of Financial Administration- Income Tax Withholding + Arkansas Department of Financial Administration - Income Tax Withholding 1 @@ -16,7 +16,7 @@ Arkansas Unemployment - Arkansas Department of Workforce Solutions - Unemployment + Arkansas Department of Workforce Solutions - Unemployment diff --git a/l10n_us_ar_hr_payroll/data/rules.xml b/l10n_us_ar_hr_payroll/data/rules.xml index 00fed6f5..71b77a79 100755 --- a/l10n_us_ar_hr_payroll/data/rules.xml +++ b/l10n_us_ar_hr_payroll/data/rules.xml @@ -11,7 +11,7 @@ code rate = payslip.dict.get_rate('US_AR_UNEMP') -year = int(payslip.dict.date_to[:4]) +year = payslip.dict.date_to.year ytd = payslip.sum('WAGE_US_AR_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01') ytd += contract.external_wages remaining = rate.wage_limit_year - ytd @@ -52,15 +52,15 @@ if result_rate == 0.0: EE: US-AR Income Tax Withholding EE_US_AR_INC_WITHHOLD python - result = not (contract.ar_w4_texarkana_exemption or contract.ar_w4_tax_exempt) + result = not (contract.ar_ar4ec_texarkana_exemption or contract.ar_ar4ec_tax_exempt) code wages = categories.GROSS annual_gross_pay = 0.00 -allowance_amt = contract.ar_w4_allowances * 26.00 +allowance_amt = contract.ar_ar4ec_allowances * 26.00 schedule_pay = contract.schedule_pay standard_deduction = 2200 -additional_withholding = contract.ar_w4_additional_wh +additional_withholding = contract.ar_ar4ec_additional_wh if contract.w4_filing_status == 'married': standard_deduction = standard_deduction * 2 diff --git a/l10n_us_ar_hr_payroll/models/hr_payroll.py b/l10n_us_ar_hr_payroll/models/hr_payroll.py index b6b51866..e477e67e 100755 --- a/l10n_us_ar_hr_payroll/models/hr_payroll.py +++ b/l10n_us_ar_hr_payroll/models/hr_payroll.py @@ -4,7 +4,7 @@ from odoo import models, fields, api class USARHrContract(models.Model): _inherit = 'hr.contract' - ar_w4_allowances = fields.Integer(string='Arkansas W-4 allowances', default=0) - ar_w4_additional_wh = fields.Float(string="Arkansas Additional Withholding", default=0.0) - ar_w4_tax_exempt = fields.Boolean(string="Tax Exempt") - ar_w4_texarkana_exemption = fields.Boolean(string="Texarkana Exemption") + ar_ar4ec_allowances = fields.Integer(string='Arkansas AR-4EC Allowances', default=0) + ar_ar4ec_additional_wh = fields.Float(string="Arkansas AR-4EC Additional Withholding", default=0.0) + ar_ar4ec_tax_exempt = fields.Boolean(string="Arkansas AR-4EC Tax Exempt") + ar_ar4ec_texarkana_exemption = fields.Boolean(string="Arkansas AR-4EC Texarkana Exemption") diff --git a/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py b/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py index 44a4a058..3f72292f 100755 --- a/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py +++ b/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py @@ -1,5 +1,4 @@ from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip -from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract class TestUsARPayslip(TestUsPayslip): @@ -31,17 +30,17 @@ class TestUsARPayslip(TestUsPayslip): process_payslip(payslip) # Make a new payslip, this one will have maximums - remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - salary if (self.AR_UNEMP_MAX_WAGE - 2*salary < salary) \ + remaining_ar_unemp_wages = self.AR_UNEMP_MAX_WAGE - salary if (self.AR_UNEMP_MAX_WAGE - 2*salary < salary) \ else salary # We reached the cap of 10000.0 in the first payslip. - self.assertEqual(0.0, remaining_AR_UNEMP_wages) + self.assertEqual(0.0, remaining_ar_unemp_wages) self._log('2019 Arkansas tax second payslip weekly:') payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_AR_UNEMP_wages) - self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_ar_unemp_wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_ar_unemp_wages * self.AR_UNEMP) def test_taxes_with_state_exempt(self): salary = 50000.0 @@ -52,7 +51,7 @@ class TestUsARPayslip(TestUsPayslip): salary, struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), ) - contract.ar_w4_tax_exempt = tax_exempt + contract.ar_ar4ec_tax_exempt = tax_exempt self._log('2019 Arkansas exempt tax first payslip weekly:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') @@ -73,7 +72,7 @@ class TestUsARPayslip(TestUsPayslip): contract = self._createContract(employee, salary, struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee')) - contract.ar_w4_texarkana_exemption = texarkana_exemption + contract.ar_ar4ec_texarkana_exemption = texarkana_exemption self._log('2019 Arkansas tax first payslip:') payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') @@ -99,8 +98,8 @@ class TestUsARPayslip(TestUsPayslip): wages, struct_id=self.ref('l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), schedule_pay=schedule_pay) - contract.ar_w4_additional_wh = 0.0 - contract.ar_w4_allowances = exemptions + contract.ar_ar4ec_additional_wh = 0.0 + contract.ar_ar4ec_allowances = exemptions self.assertEqual(contract.schedule_pay, 'monthly') @@ -114,7 +113,7 @@ class TestUsARPayslip(TestUsPayslip): # TODO: change to hand the test_ar_amt already be divided by pay periods self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) - contract.ar_w4_additional_wh = additional_wh + contract.ar_ar4ec_additional_wh = additional_wh payslip.compute_sheet() cats = self._getCategories(payslip) @@ -137,8 +136,8 @@ class TestUsARPayslip(TestUsPayslip): struct_id=self.ref( 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), schedule_pay=schedule_pay) - contract.ar_w4_additional_wh = 0.0 - contract.ar_w4_allowances = exemptions + contract.ar_ar4ec_additional_wh = 0.0 + contract.ar_ar4ec_allowances = exemptions self.assertEqual(contract.schedule_pay, 'monthly') @@ -151,7 +150,7 @@ class TestUsARPayslip(TestUsPayslip): self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) - contract.ar_w4_additional_wh = additional_wh + contract.ar_ar4ec_additional_wh = additional_wh payslip.compute_sheet() cats = self._getCategories(payslip) self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) @@ -160,7 +159,7 @@ class TestUsARPayslip(TestUsPayslip): # Make a new payslip, this one will have maximums - remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - wages if (self.AR_UNEMP_MAX_WAGE - 2 * wages < wages) \ + remaining_ar_unemp_wages = self.AR_UNEMP_MAX_WAGE - wages if (self.AR_UNEMP_MAX_WAGE - 2 * wages < wages) \ else wages self._log('2019 Arkansas tax second payslip weekly:') @@ -168,8 +167,8 @@ class TestUsARPayslip(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_AR_UNEMP_wages) - self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_ar_unemp_wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_ar_unemp_wages * self.AR_UNEMP) def test_over_fifty_thousand(self): wages = 10000.00 # 10000.00 monthly is over 50,000 annually. @@ -186,8 +185,8 @@ class TestUsARPayslip(TestUsPayslip): struct_id=self.ref( 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), schedule_pay=schedule_pay) - contract.ar_w4_additional_wh = 0.0 - contract.ar_w4_allowances = exemptions + contract.ar_ar4ec_additional_wh = 0.0 + contract.ar_ar4ec_allowances = exemptions self.assertEqual(contract.schedule_pay, 'monthly') @@ -200,7 +199,7 @@ class TestUsARPayslip(TestUsPayslip): self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) - contract.ar_w4_additional_wh = additional_wh + contract.ar_ar4ec_additional_wh = additional_wh payslip.compute_sheet() cats = self._getCategories(payslip) self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) @@ -209,7 +208,7 @@ class TestUsARPayslip(TestUsPayslip): # Make a new payslip, this one will have maximums - remaining_AR_UNEMP_wages = self.AR_UNEMP_MAX_WAGE - wages if (self.AR_UNEMP_MAX_WAGE - 2 * wages < wages) \ + remaining_ar_unemp_wages = self.AR_UNEMP_MAX_WAGE - wages if (self.AR_UNEMP_MAX_WAGE - 2 * wages < wages) \ else wages self._log('2019 Arkansas tax second payslip weekly:') @@ -217,8 +216,8 @@ class TestUsARPayslip(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_AR_UNEMP_wages) - self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_AR_UNEMP_wages * self.AR_UNEMP) + self.assertPayrollEqual(cats['WAGE_US_AR_UNEMP'], remaining_ar_unemp_wages) + self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], remaining_ar_unemp_wages * self.AR_UNEMP) def test_married(self): wages = 5500.00 @@ -237,8 +236,8 @@ class TestUsARPayslip(TestUsPayslip): struct_id=self.ref( 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), schedule_pay=schedule_pay) - contract.ar_w4_additional_wh = additional_wh - contract.ar_w4_allowances = exemptions + contract.ar_ar4ec_additional_wh = additional_wh + contract.ar_ar4ec_allowances = exemptions contract.w4_filing_status = w4_filing_status self.assertEqual(contract.w4_filing_status, 'married') @@ -269,8 +268,8 @@ class TestUsARPayslip(TestUsPayslip): struct_id=self.ref( 'l10n_us_ar_hr_payroll.hr_payroll_salary_structure_us_ar_employee'), schedule_pay=schedule_pay) - contract.ar_w4_additional_wh = 0 - contract.ar_w4_allowances = exemptions + contract.ar_ar4ec_additional_wh = 0 + contract.ar_ar4ec_allowances = exemptions contract.w4_filling_status = w4_filling_status self.assertEqual(contract.w4_filling_status, 'single') @@ -284,7 +283,7 @@ class TestUsARPayslip(TestUsPayslip): self.assertPayrollEqual(cats['ER_US_AR_UNEMP'], cats['WAGE_US_AR_UNEMP'] * self.AR_UNEMP) self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods)) - contract.ar_w4_additional_wh = additional_wh + contract.ar_ar4ec_additional_wh = additional_wh payslip.compute_sheet() cats = self._getCategories(payslip) self.assertPayrollEqual(cats['EE_US_AR_INC_WITHHOLD'], -(test_ar_amt / pay_periods) - additional_wh) diff --git a/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml b/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml index d1ed5d9c..e3fc69be 100755 --- a/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml +++ b/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml @@ -10,10 +10,10 @@ - - - - + + + + From 853e8f8b13750e880985bf2a2a800173ccf4a0fc Mon Sep 17 00:00:00 2001 From: David Frick Date: Thu, 11 Jul 2019 16:46:42 -0400 Subject: [PATCH 14/36] NEW `l10n_us_ia_hr_payroll` for 11.0 --- l10n_us_ia_hr_payroll/__init__.py | 1 + l10n_us_ia_hr_payroll/__manifest__.py | 29 +++ l10n_us_ia_hr_payroll/data/base.xml | 47 +++++ l10n_us_ia_hr_payroll/data/final.xml | 17 ++ l10n_us_ia_hr_payroll/data/rates.xml | 14 ++ l10n_us_ia_hr_payroll/data/rules.xml | 159 +++++++++++++++ l10n_us_ia_hr_payroll/models/__init__.py | 1 + l10n_us_ia_hr_payroll/models/hr_payroll.py | 9 + l10n_us_ia_hr_payroll/tests/__init__.py | 1 + .../tests/test_us_ia_payslip.py | 192 ++++++++++++++++++ .../views/hr_payroll_views.xml | 22 ++ 11 files changed, 492 insertions(+) create mode 100755 l10n_us_ia_hr_payroll/__init__.py create mode 100755 l10n_us_ia_hr_payroll/__manifest__.py create mode 100755 l10n_us_ia_hr_payroll/data/base.xml create mode 100755 l10n_us_ia_hr_payroll/data/final.xml create mode 100755 l10n_us_ia_hr_payroll/data/rates.xml create mode 100755 l10n_us_ia_hr_payroll/data/rules.xml create mode 100644 l10n_us_ia_hr_payroll/models/__init__.py create mode 100755 l10n_us_ia_hr_payroll/models/hr_payroll.py create mode 100755 l10n_us_ia_hr_payroll/tests/__init__.py create mode 100755 l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py create mode 100755 l10n_us_ia_hr_payroll/views/hr_payroll_views.xml diff --git a/l10n_us_ia_hr_payroll/__init__.py b/l10n_us_ia_hr_payroll/__init__.py new file mode 100755 index 00000000..0650744f --- /dev/null +++ b/l10n_us_ia_hr_payroll/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_us_ia_hr_payroll/__manifest__.py b/l10n_us_ia_hr_payroll/__manifest__.py new file mode 100755 index 00000000..28dc125d --- /dev/null +++ b/l10n_us_ia_hr_payroll/__manifest__.py @@ -0,0 +1,29 @@ +{ + 'name': 'USA - Iowa - Payroll', + 'author': 'Hibou Corp. ', + 'license': 'AGPL-3', + 'category': 'Localization', + 'depends': ['l10n_us_hr_payroll'], + 'version': '11.0.2019.0.0', + 'description': """ +USA::Iowa Payroll Rules. +================================== + +* Contribution register and partner for Additional WithholdingTaxaction - Income Tax Withholding +* Contribution register and partner for Iowa Workforce Development- Unemployment +* Contract level Iowa Exemptions +* Company level Iowa Unemployment Rate +* Salary Structure for Iowa + """, + + 'auto_install': False, + 'website': 'https://hibou.io/', + 'data': [ + 'views/hr_payroll_views.xml', + 'data/base.xml', + 'data/rates.xml', + 'data/rules.xml', + 'data/final.xml', + ], + 'installable': True +} diff --git a/l10n_us_ia_hr_payroll/data/base.xml b/l10n_us_ia_hr_payroll/data/base.xml new file mode 100755 index 00000000..6e5a2391 --- /dev/null +++ b/l10n_us_ia_hr_payroll/data/base.xml @@ -0,0 +1,47 @@ + + + + + + Iowa Workforce Development- Unemployment Tax + 1 + + + + Iowa Department of Revenue - Income Tax Withholding + 1 + + + + + + Iowa Unemployment + Iowa Workforce Development - Unemployment + + + + Iowa Income Tax Withholding + Iowa Department of Revenue - Income Tax Withholding + + + + + + + Wage: US-IA Unemployment + WAGE_US_IA_UNEMP + + + + ER: US-IA Unemployment + ER_US_IA_UNEMP + + + + + EE: US-IA Income Tax Withholding + EE_US_IA_INC_WITHHOLD + + + + diff --git a/l10n_us_ia_hr_payroll/data/final.xml b/l10n_us_ia_hr_payroll/data/final.xml new file mode 100755 index 00000000..58b73b5a --- /dev/null +++ b/l10n_us_ia_hr_payroll/data/final.xml @@ -0,0 +1,17 @@ + + + + + + US_IA_EMP + USA Iowa Employee + + + + + + diff --git a/l10n_us_ia_hr_payroll/data/rates.xml b/l10n_us_ia_hr_payroll/data/rates.xml new file mode 100755 index 00000000..6c910f0c --- /dev/null +++ b/l10n_us_ia_hr_payroll/data/rates.xml @@ -0,0 +1,14 @@ + + + + + + US IA Unemployment + US_IA_UNEMP + 1.0 + 2019-01-01 + + + + + diff --git a/l10n_us_ia_hr_payroll/data/rules.xml b/l10n_us_ia_hr_payroll/data/rules.xml new file mode 100755 index 00000000..eebebd47 --- /dev/null +++ b/l10n_us_ia_hr_payroll/data/rules.xml @@ -0,0 +1,159 @@ + + + + + + + + Wage: US-IA Unemployment + WAGE_US_IA_UNEMP + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +rate = payslip.dict.get_rate('US_IA_UNEMP') +year = int(payslip.dict.date_to[:4]) +ytd = payslip.sum('WAGE_US_IA_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01') +ytd += contract.external_wages +remaining = rate.wage_limit_year - ytd +if remaining <= 0.0: + result = 0 +elif remaining < categories.BASIC: + result = remaining +else: + result = categories.BASIC + + + + + + + + ER: US-IA Unemployment + ER_US_IA_UNEMP + python + result = (contract.futa_type != contract.FUTA_TYPE_BASIC) + code + +rate = payslip.dict.get_rate('US_IA_UNEMP') +result_rate = -rate.rate +result = categories.WAGE_US_IA_UNEMP + +# result_rate of 0 implies 100% due to bug +if result_rate == 0.0: + result = 0.0 + + + + + + + + + EE: US-IA Income Tax Withholding + EE_US_IA_INC_WITHHOLD + python + result = not contract.ia_w4_tax_exempt + code + +wages = categories.GROSS +federal_withholding = categories.EE_US_FED_INC_WITHHOLD +schedule_pay = contract.schedule_pay +allowances = contract.ia_w4_allowances +# It is + federal_withholding because federal_withholding is negative. +t1 = wages + federal_withholding +standard_deduction_table = { + 'daily': (6.50, 16.00), + 'weekly': (32.50, 80.00), + 'bi-weekly': (65.00, 160.00), + 'semi-monthly': (70.42, 173.33), + 'monthly': (140.83, 346.67), + 'annually': (1690.00, 4160.00)} +t2 = t1 - standard_deduction_table[schedule_pay][0] if allowances < 2 else standard_deduction_table[schedule_pay][1] +# IMPORTANT -- ALL RATES ARE ALREADY DIVIDED BY 100 -> 8.53% is in the table as 0.0853 +if schedule_pay == 'weekly': + tax_rate_table = [ + (25.63, 0.0033, 0.0), + (51.27, 0.0067, 0.08), + (102.52, 0.0225, 0.025), + (230.67, 0.0414, 1.40), + (384.46, 0.0563, 6.71), + (512.62, 0.0596, 15.37), + (768.92, 0.0625, 23.01), + (1153.38, 0.0744, 39.03), + (float('inf'), 0.0853, 67.63), + ] +elif schedule_pay == 'bi-weekly': + tax_rate_table = [ + (51.27, 0.0033, 0.00), + (102.54, 0.0067, 0.17), + (205.04, 0.00225, 0.51), + (461.35, 0.0414, 2.82), + (768.92, 0.0563, 13.43), + (1025.23, 0.0596, 30.75), + (1537.85, 0.0625, 46.03), + (2306.77, 0.0744, 78.07), + (float('inf'), 0.0853, 135.28) + ] +elif schedule_pay == 'semi-monthly': + tax_rate_table = [ + (55.54, 0.0033, 0.00), + (111.08, 0.0067, 0.18), + (222.13, 0.0225, 0.55), + (499.79, 0.0414, 3.05), + (833.00, 0.0563, 14.59), + (1110.67, 0.0596, 33.31), + (1666.00, 0.0625, 49.86), + (2499.00, 0.0744, 84.57), + (float('inf'), 0.0853, 146.55) + ] +elif schedule_pay == 'monthly': + tax_rate_table = [ + (111.08, 0.0033, 0.00), + (222.17, 0.0067, 0.37), + (444.25, 0.0225, 1.11), + (999.58, 0.0414, 6.11), + (1666.00, 0.0563, 29.10), + (2221.33, 0.0596, 62.66), + (3332.00, 0.0625, 99.72), + (4998.00, 0.0744, 169.14), + (float('inf'), 0.0853, 293.09) + ] +elif schedule_pay == 'annual': + tax_rate_table = [ + (1333.00, 0.0033, 0.00), + (2666.00, 0.0067, 4.40), + (5331.00, 0.0225, 13.33), + (11995.00, 0.0414, 73.29), + (19992.00, 0.0563, 349.19), + (26656.00, 0.0596, 799.41), + (39984.00, 0.0625, 1196.58), + (59976.00, 0.0744, 2029.58), + (float('inf'), 0.0853, 3516.98) + ] + +t3 = 0.0 +last = 0.0 +for row in tax_rate_table: + cap, rate, flat_fee = row + if cap > t2: + taxed_amount = t2 - last + t3 = flat_fee + (rate * taxed_amount) + break + last = cap + +deduction_per_allowance = { + 'daily': 0.15, + 'weekly': 0.77, + 'bi-weekly': 1.54, + 'semi-monthly': 3.33, + 'annually': 40.00 + } +t4 = t3 - (deduction_per_allowance[schedule_pay] * allowances) +t5 = t4 + contract.ia_w4_additional_wh +result = -t5 + + + + + diff --git a/l10n_us_ia_hr_payroll/models/__init__.py b/l10n_us_ia_hr_payroll/models/__init__.py new file mode 100644 index 00000000..e99aa24a --- /dev/null +++ b/l10n_us_ia_hr_payroll/models/__init__.py @@ -0,0 +1 @@ +from . import hr_payroll diff --git a/l10n_us_ia_hr_payroll/models/hr_payroll.py b/l10n_us_ia_hr_payroll/models/hr_payroll.py new file mode 100755 index 00000000..c171154a --- /dev/null +++ b/l10n_us_ia_hr_payroll/models/hr_payroll.py @@ -0,0 +1,9 @@ +from odoo import models, fields, api + + +class USIAHrContract(models.Model): + _inherit = 'hr.contract' + + ia_w4_allowances = fields.Integer(string='Iowa W-4 allowances', default=0) + ia_w4_additional_wh = fields.Float(string="Iowa Additional Withholding", default=0.0) + ia_w4_tax_exempt = fields.Boolean(string="Tax Exempt") diff --git a/l10n_us_ia_hr_payroll/tests/__init__.py b/l10n_us_ia_hr_payroll/tests/__init__.py new file mode 100755 index 00000000..4f771ce0 --- /dev/null +++ b/l10n_us_ia_hr_payroll/tests/__init__.py @@ -0,0 +1 @@ +from . import test_us_ia_payslip diff --git a/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py b/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py new file mode 100755 index 00000000..53d1fd44 --- /dev/null +++ b/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py @@ -0,0 +1,192 @@ +from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip +from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract + + +class TestUsIAPayslip(TestUsPayslip): + IA_UNEMP_MAX_WAGE = 30600 + IA_UNEMP = -1.0 / 100.0 + IA_INC_TAX = -0.0535 + + def test_taxes_weekly(self): + wages = 30000.00 + schedule_pay = 'weekly' + allowances = 1 + additional_wh = 0.00 + employee = self._createEmployee() + contract = self._createContract(employee, wages, + struct_id=self.ref('l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), + schedule_pay=schedule_pay) + contract.ia_w4_allowances = allowances + contract.ia_w4_additional_wh = additional_wh + + self.assertEqual(contract.schedule_pay, 'weekly') + + self._log('2019 Iowa tax first payslip weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal + # withholding amount because it is calculated in the base US payroll module as a negative + # t1 = 30000 - (10399.66) = 19600.34 + t1_to_test = wages + cats['EE_US_FED_INC_WITHHOLD'] + self.assertPayrollEqual(t1_to_test, 19600.34) + + # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. + # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances, + # and 80.00 of 2 or more allowances. + standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use. + # t2 = 19600.34 - 32.50 = 19567.84 + t2_to_test = t1_to_test - standard_deduction + self.assertPayrollEqual(t2_to_test, 19567.84) + # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. + # 1153.38 is the bracket floor. 8.53 is the rate, and 67.63 is the flat fee. + # t3 = 1638.38 + t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63 + self.assertPayrollEqual(t3_to_test, 1638.38) + # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly + # deduction amount per allowance is 0.77 + # t4 = 1638.38 - 0.77 = 155.03 + t4_to_test = t3_to_test - (0.77 * allowances) + self.assertPayrollEqual(t4_to_test, 1637.61) + # t5 is our T4 plus the additional withholding per period + # t5 = 1637.61 + 0.0 + # Convert to negative as well. + t5_to_test = -t4_to_test - additional_wh + self.assertPayrollEqual(t5_to_test, -1637.61) + + self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], cats['WAGE_US_IA_UNEMP'] * self.IA_UNEMP) + self.assertPayrollEqual(cats['EE_US_IA_INC_WITHHOLD'], t5_to_test) + + # Test additional + additional_wh = 15.00 + contract.ia_w4_additional_wh = additional_wh + payslip.compute_sheet() + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats['EE_US_IA_INC_WITHHOLD'], t5_to_test - additional_wh) + + process_payslip(payslip) + + # Make a new payslip, this one will have maximums + + remaining_IA_UNEMP_wages = self.IA_UNEMP_MAX_WAGE - wages if (self.IA_UNEMP_MAX_WAGE - 2*wages < wages) \ + else wages + + self._log('2019 Iowa tax second payslip weekly:') + payslip = self._createPayslip(employee, '2019-02-01', '2019-02-28') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], remaining_IA_UNEMP_wages) + self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], remaining_IA_UNEMP_wages * self.IA_UNEMP) + + def test_taxes_biweekly(self): + wages = 3000.00 + schedule_pay = 'bi-weekly' + allowances = 1 + additional_wh = 0.00 + employee = self._createEmployee() + contract = self._createContract(employee, wages, + struct_id=self.ref( + 'l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), + schedule_pay=schedule_pay) + contract.ia_w4_allowances = allowances + contract.ia_w4_additional_wh = additional_wh + + self.assertEqual(contract.schedule_pay, 'bi-weekly') + + self._log('2019 Iowa tax first payslip bi-weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal + # withholding amount because it is calculated in the base US payroll module as a negative + t1_to_test = wages + cats['EE_US_FED_INC_WITHHOLD'] + # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. + # In our case, we have a biweekly period which on the table has a std deduct. of $65.00 for 0 or 1 allowances, + # and $160.00 of 2 or more allowances. + standard_deduction = 65.00 # The allowance tells us what standard_deduction amount to use. + t2_to_test = t1_to_test - standard_deduction + # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. + t3_to_test = ((t2_to_test - 2306.77) * (8.53 / 100)) + 135.28 + # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly + # deduction amount per allowance is 0.77 + t4_to_test = t3_to_test - (1.54 * allowances) + # t5 is our T4 plus the additional withholding per period + t5_to_test = -t4_to_test - additional_wh + + self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], cats['WAGE_US_IA_UNEMP'] * self.IA_UNEMP) + self.assertPayrollEqual(cats['EE_US_IA_INC_WITHHOLD'], t5_to_test - additional_wh) + + process_payslip(payslip) + + def test_taxes_with_external_weekly(self): + wages = 2500.00 + schedule_pay = 'weekly' + allowances = 1 + additional_wh = 0.00 + external_wages = 500.0 + + employee = self._createEmployee() + contract = self._createContract(employee, wages, external_wages=external_wages, + struct_id=self.ref('l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), + schedule_pay=schedule_pay) + contract.ia_w4_additional_wh = additional_wh + contract.ia_w4_allowances = allowances + + self._log('2019 Iowa external tax first payslip external weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + + # T1 is the gross taxable wages for the pay period minus the Federal withholding amount. We add the federal + # withholding amount because it is calculated in the base US payroll module as a negative + t1_to_test = wages + cats['EE_US_FED_INC_WITHHOLD'] + # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. + # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances, + # and 80.00 of 2 or more allowances. + standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use. + t2_to_test = t1_to_test - standard_deduction + # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. + t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63 + # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly + # deduction amount per allowance is 0.77 + t4_to_test = t3_to_test - (0.77 * allowances) + # t5 is our T4 plus the additional withholding per period + t5_to_test = -t4_to_test - additional_wh + + self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], wages) + self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], cats['WAGE_US_IA_UNEMP'] * self.IA_UNEMP) + self.assertPayrollEqual(cats['EE_US_IA_INC_WITHHOLD'], t5_to_test) + + process_payslip(payslip) + + def test_taxes_with_state_exempt_weekly(self): + salary = 5000.0 + external_wages = 10000.0 + schedule_pay = 'weekly' + + employee = self._createEmployee() + contract = self._createContract(employee, + salary, + external_wages=external_wages, + struct_id=self.ref('l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), + futa_type=USHrContract.FUTA_TYPE_BASIC, + schedule_pay=schedule_pay) + contract.ia_w4_tax_exempt = True + + self._log('2019 Iowa exempt tax first payslip exempt weekly:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + payslip.compute_sheet() + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats.get('WAGE_US_IA_UNEMP', 0.0), 0.0) + self.assertPayrollEqual(cats.get('ER_US_IA_UNEMP', 0.0), cats.get('WAGE_US_IA_UNEMP', 0.0) * self.IA_UNEMP) + + + + diff --git a/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml b/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml new file mode 100755 index 00000000..f061cceb --- /dev/null +++ b/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml @@ -0,0 +1,22 @@ + + + + + hr.contract.form.inherit + hr.contract + 119 + + + + + + + + + + + + + + + From a9d35b4c6b43ca5f864b1fbd7c146ddd42219f9b Mon Sep 17 00:00:00 2001 From: David Frick Date: Mon, 22 Jul 2019 15:41:47 -0400 Subject: [PATCH 15/36] FIX 'l10n_us_ia_hr_payroll` missing mothnly allowance per standard deduction --- l10n_us_ia_hr_payroll/data/rules.xml | 9 +++++---- l10n_us_ia_hr_payroll/views/hr_payroll_views.xml | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/l10n_us_ia_hr_payroll/data/rules.xml b/l10n_us_ia_hr_payroll/data/rules.xml index eebebd47..2281d900 100755 --- a/l10n_us_ia_hr_payroll/data/rules.xml +++ b/l10n_us_ia_hr_payroll/data/rules.xml @@ -69,7 +69,7 @@ standard_deduction_table = { 'semi-monthly': (70.42, 173.33), 'monthly': (140.83, 346.67), 'annually': (1690.00, 4160.00)} -t2 = t1 - standard_deduction_table[schedule_pay][0] if allowances < 2 else standard_deduction_table[schedule_pay][1] +t2 = t1 - standard_deduction_table[schedule_pay][0] if (allowances < 2) else standard_deduction_table[schedule_pay][1] # IMPORTANT -- ALL RATES ARE ALREADY DIVIDED BY 100 -> 8.53% is in the table as 0.0853 if schedule_pay == 'weekly': tax_rate_table = [ @@ -136,7 +136,7 @@ t3 = 0.0 last = 0.0 for row in tax_rate_table: cap, rate, flat_fee = row - if cap > t2: + if cap > t2: taxed_amount = t2 - last t3 = flat_fee + (rate * taxed_amount) break @@ -146,8 +146,9 @@ deduction_per_allowance = { 'daily': 0.15, 'weekly': 0.77, 'bi-weekly': 1.54, - 'semi-monthly': 3.33, - 'annually': 40.00 + 'semi-monthly': 1.67, + 'monthly': 3.33, + 'annually': 40.00, } t4 = t3 - (deduction_per_allowance[schedule_pay] * allowances) t5 = t4 + contract.ia_w4_additional_wh diff --git a/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml b/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml index f061cceb..4b822364 100755 --- a/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml +++ b/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml @@ -1,5 +1,6 @@ + hr.contract.form.inherit @@ -19,4 +20,5 @@ + From aa5c1d76ccfd6bdfee7b3fe213801e6ae0828d7e Mon Sep 17 00:00:00 2001 From: David Frick Date: Wed, 14 Aug 2019 18:28:19 -0400 Subject: [PATCH 16/36] MIG `l10n_us_ar_hr_payroll` added oldname to fields for remapping --- l10n_us_ar_hr_payroll/models/hr_payroll.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/l10n_us_ar_hr_payroll/models/hr_payroll.py b/l10n_us_ar_hr_payroll/models/hr_payroll.py index e477e67e..2bb52b2c 100755 --- a/l10n_us_ar_hr_payroll/models/hr_payroll.py +++ b/l10n_us_ar_hr_payroll/models/hr_payroll.py @@ -4,7 +4,11 @@ from odoo import models, fields, api class USARHrContract(models.Model): _inherit = 'hr.contract' - ar_ar4ec_allowances = fields.Integer(string='Arkansas AR-4EC Allowances', default=0) - ar_ar4ec_additional_wh = fields.Float(string="Arkansas AR-4EC Additional Withholding", default=0.0) - ar_ar4ec_tax_exempt = fields.Boolean(string="Arkansas AR-4EC Tax Exempt") - ar_ar4ec_texarkana_exemption = fields.Boolean(string="Arkansas AR-4EC Texarkana Exemption") + ar_ar4ec_allowances = fields.Integer(string='Arkansas AR-4EC Allowances', + oldname='ar_w4_allowances') + ar_ar4ec_additional_wh = fields.Float(string="Arkansas AR-4EC Additional Withholding", + oldname='ar_w4_additional_wh') + ar_ar4ec_tax_exempt = fields.Boolean(string='Arkansas AR-4EC Tax Exempt', + oldname='ar_w4_tax_exempt') + ar_ar4ec_texarkana_exemption = fields.Boolean(string='Arkansas AR-4EC Texarkana Exemption', + oldname='ar_w4_texarkana_exemption') From 71efc67e0567b580d6ae830fe83d16213b9c1794 Mon Sep 17 00:00:00 2001 From: David Frick Date: Wed, 14 Aug 2019 18:44:22 -0400 Subject: [PATCH 17/36] MIG `l10n_us_ia_hr_payroll` for 12.0. removed default values of fields, and removed superficial tests. --- l10n_us_ia_hr_payroll/__manifest__.py | 8 ++--- l10n_us_ia_hr_payroll/data/rules.xml | 2 +- l10n_us_ia_hr_payroll/models/hr_payroll.py | 6 ++-- .../tests/test_us_ia_payslip.py | 30 +++++++------------ 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/l10n_us_ia_hr_payroll/__manifest__.py b/l10n_us_ia_hr_payroll/__manifest__.py index 28dc125d..7cc7b1d0 100755 --- a/l10n_us_ia_hr_payroll/__manifest__.py +++ b/l10n_us_ia_hr_payroll/__manifest__.py @@ -4,13 +4,13 @@ 'license': 'AGPL-3', 'category': 'Localization', 'depends': ['l10n_us_hr_payroll'], - 'version': '11.0.2019.0.0', + 'version': '12.0.2019.0.0', 'description': """ -USA::Iowa Payroll Rules. +USA - Iowa Payroll Rules. ================================== -* Contribution register and partner for Additional WithholdingTaxaction - Income Tax Withholding -* Contribution register and partner for Iowa Workforce Development- Unemployment +* Contribution register and partner for Iowa Workforce Development (IWD) - Unemployment +* Contribution register and partner for Iowa Department of Revenue (IDOR) - Income Tax Withholding * Contract level Iowa Exemptions * Company level Iowa Unemployment Rate * Salary Structure for Iowa diff --git a/l10n_us_ia_hr_payroll/data/rules.xml b/l10n_us_ia_hr_payroll/data/rules.xml index 2281d900..62ec8a59 100755 --- a/l10n_us_ia_hr_payroll/data/rules.xml +++ b/l10n_us_ia_hr_payroll/data/rules.xml @@ -12,7 +12,7 @@ code rate = payslip.dict.get_rate('US_IA_UNEMP') -year = int(payslip.dict.date_to[:4]) +year = payslip.dict.date_to.year ytd = payslip.sum('WAGE_US_IA_UNEMP', str(year) + '-01-01', str(year+1) + '-01-01') ytd += contract.external_wages remaining = rate.wage_limit_year - ytd diff --git a/l10n_us_ia_hr_payroll/models/hr_payroll.py b/l10n_us_ia_hr_payroll/models/hr_payroll.py index c171154a..7eac3055 100755 --- a/l10n_us_ia_hr_payroll/models/hr_payroll.py +++ b/l10n_us_ia_hr_payroll/models/hr_payroll.py @@ -4,6 +4,6 @@ from odoo import models, fields, api class USIAHrContract(models.Model): _inherit = 'hr.contract' - ia_w4_allowances = fields.Integer(string='Iowa W-4 allowances', default=0) - ia_w4_additional_wh = fields.Float(string="Iowa Additional Withholding", default=0.0) - ia_w4_tax_exempt = fields.Boolean(string="Tax Exempt") + ia_w4_allowances = fields.Integer(string='Iowa W-4 allowances') + ia_w4_additional_wh = fields.Float(string="Iowa W-4 Additional Withholding") + ia_w4_tax_exempt = fields.Boolean(string="Iowa W-4 Tax Exempt") diff --git a/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py b/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py index 53d1fd44..bde22b29 100755 --- a/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py +++ b/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py @@ -1,5 +1,4 @@ from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip -from odoo.addons.l10n_us_hr_payroll.models.l10n_us_hr_payroll import USHrContract class TestUsIAPayslip(TestUsPayslip): @@ -11,13 +10,11 @@ class TestUsIAPayslip(TestUsPayslip): wages = 30000.00 schedule_pay = 'weekly' allowances = 1 - additional_wh = 0.00 employee = self._createEmployee() contract = self._createContract(employee, wages, struct_id=self.ref('l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), schedule_pay=schedule_pay) contract.ia_w4_allowances = allowances - contract.ia_w4_additional_wh = additional_wh self.assertEqual(contract.schedule_pay, 'weekly') @@ -30,30 +27,24 @@ class TestUsIAPayslip(TestUsPayslip): # withholding amount because it is calculated in the base US payroll module as a negative # t1 = 30000 - (10399.66) = 19600.34 t1_to_test = wages + cats['EE_US_FED_INC_WITHHOLD'] - self.assertPayrollEqual(t1_to_test, 19600.34) - # T2 is T1 minus our standard deduction which is a table of flat rates dependent on the number of allowances. # In our case, we have a weekly period which on the table has a std deduct. of $32.50 for 0 or 1 allowances, # and 80.00 of 2 or more allowances. standard_deduction = 32.50 # The allowance tells us what standard_deduction amount to use. # t2 = 19600.34 - 32.50 = 19567.84 t2_to_test = t1_to_test - standard_deduction - self.assertPayrollEqual(t2_to_test, 19567.84) # T3 is T2 multiplied by the income rates in the large table plus a flat fee for that bracket. # 1153.38 is the bracket floor. 8.53 is the rate, and 67.63 is the flat fee. # t3 = 1638.38 t3_to_test = ((t2_to_test - 1153.38) * (8.53 / 100)) + 67.63 - self.assertPayrollEqual(t3_to_test, 1638.38) # T4 is T3 minus a flat amount determined by pay period * the number of deductions. For 2019, our weekly # deduction amount per allowance is 0.77 # t4 = 1638.38 - 0.77 = 155.03 t4_to_test = t3_to_test - (0.77 * allowances) - self.assertPayrollEqual(t4_to_test, 1637.61) # t5 is our T4 plus the additional withholding per period # t5 = 1637.61 + 0.0 # Convert to negative as well. - t5_to_test = -t4_to_test - additional_wh - self.assertPayrollEqual(t5_to_test, -1637.61) + t5_to_test = -t4_to_test self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], wages) self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], cats['WAGE_US_IA_UNEMP'] * self.IA_UNEMP) @@ -70,7 +61,7 @@ class TestUsIAPayslip(TestUsPayslip): # Make a new payslip, this one will have maximums - remaining_IA_UNEMP_wages = self.IA_UNEMP_MAX_WAGE - wages if (self.IA_UNEMP_MAX_WAGE - 2*wages < wages) \ + remaining_ia_unemp_wages = self.IA_UNEMP_MAX_WAGE - wages if (self.IA_UNEMP_MAX_WAGE - 2*wages < wages) \ else wages self._log('2019 Iowa tax second payslip weekly:') @@ -78,21 +69,19 @@ class TestUsIAPayslip(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], remaining_IA_UNEMP_wages) - self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], remaining_IA_UNEMP_wages * self.IA_UNEMP) + self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], remaining_ia_unemp_wages) + self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], remaining_ia_unemp_wages * self.IA_UNEMP) def test_taxes_biweekly(self): wages = 3000.00 schedule_pay = 'bi-weekly' allowances = 1 - additional_wh = 0.00 employee = self._createEmployee() contract = self._createContract(employee, wages, struct_id=self.ref( 'l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), schedule_pay=schedule_pay) contract.ia_w4_allowances = allowances - contract.ia_w4_additional_wh = additional_wh self.assertEqual(contract.schedule_pay, 'bi-weekly') @@ -115,11 +104,11 @@ class TestUsIAPayslip(TestUsPayslip): # deduction amount per allowance is 0.77 t4_to_test = t3_to_test - (1.54 * allowances) # t5 is our T4 plus the additional withholding per period - t5_to_test = -t4_to_test - additional_wh + t5_to_test = -t4_to_test self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], wages) self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], cats['WAGE_US_IA_UNEMP'] * self.IA_UNEMP) - self.assertPayrollEqual(cats['EE_US_IA_INC_WITHHOLD'], t5_to_test - additional_wh) + self.assertPayrollEqual(cats['EE_US_IA_INC_WITHHOLD'], t5_to_test) process_payslip(payslip) @@ -175,7 +164,6 @@ class TestUsIAPayslip(TestUsPayslip): salary, external_wages=external_wages, struct_id=self.ref('l10n_us_ia_hr_payroll.hr_payroll_salary_structure_us_ia_employee'), - futa_type=USHrContract.FUTA_TYPE_BASIC, schedule_pay=schedule_pay) contract.ia_w4_tax_exempt = True @@ -184,8 +172,10 @@ class TestUsIAPayslip(TestUsPayslip): payslip.compute_sheet() cats = self._getCategories(payslip) - self.assertPayrollEqual(cats.get('WAGE_US_IA_UNEMP', 0.0), 0.0) - self.assertPayrollEqual(cats.get('ER_US_IA_UNEMP', 0.0), cats.get('WAGE_US_IA_UNEMP', 0.0) * self.IA_UNEMP) + self.assertPayrollEqual(cats['WAGE_US_IA_UNEMP'], salary) + self.assertPayrollEqual(cats['ER_US_IA_UNEMP'], cats['WAGE_US_IA_UNEMP'] * self.IA_UNEMP) + self.assertPayrollEqual(cats.get('EE_US_IA_INC_WITHHOLD', 0.00), 0.00) + From 1fb03d8d7372e2d09ff90ca74bf783c2300457c8 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 5 Jun 2018 11:30:29 -0700 Subject: [PATCH 18/36] Initial commit of `product_catch_weight` for 11.0 --- product_catch_weight/__init__.py | 1 + product_catch_weight/__manifest__.py | 19 +++ product_catch_weight/models/__init__.py | 3 + .../models/account_invoice.py | 42 +++++ product_catch_weight/models/stock.py | 20 +++ product_catch_weight/models/stock_patch.py | 114 +++++++++++++ product_catch_weight/tests/__init__.py | 1 + .../tests/test_catch_weight.py | 152 ++++++++++++++++++ product_catch_weight/views/stock_views.xml | 35 ++++ 9 files changed, 387 insertions(+) create mode 100644 product_catch_weight/__init__.py create mode 100644 product_catch_weight/__manifest__.py create mode 100644 product_catch_weight/models/__init__.py create mode 100644 product_catch_weight/models/account_invoice.py create mode 100644 product_catch_weight/models/stock.py create mode 100644 product_catch_weight/models/stock_patch.py create mode 100644 product_catch_weight/tests/__init__.py create mode 100644 product_catch_weight/tests/test_catch_weight.py create mode 100644 product_catch_weight/views/stock_views.xml diff --git a/product_catch_weight/__init__.py b/product_catch_weight/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/product_catch_weight/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_catch_weight/__manifest__.py b/product_catch_weight/__manifest__.py new file mode 100644 index 00000000..7c2ef68c --- /dev/null +++ b/product_catch_weight/__manifest__.py @@ -0,0 +1,19 @@ +{ + 'name': 'Product Catch Weight', + 'version': '11.0.1.0.0', + 'category': 'Warehouse', + 'depends': [ + 'sale_stock', + 'purchase', + ], + 'description': """ + """, + 'author': 'Hibou Corp.', + 'license': 'AGPL-3', + 'website': 'https://hibou.io/', + 'data': [ + 'views/stock_views.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/product_catch_weight/models/__init__.py b/product_catch_weight/models/__init__.py new file mode 100644 index 00000000..57a87093 --- /dev/null +++ b/product_catch_weight/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_invoice +from . import stock_patch +from . import stock diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py new file mode 100644 index 00000000..ab72943c --- /dev/null +++ b/product_catch_weight/models/account_invoice.py @@ -0,0 +1,42 @@ +from odoo import api, fields, models +import logging + +_logger = logging.getLogger(__name__) + + +class AccountInvoiceLine(models.Model): + _inherit = 'account.invoice.line' + + @api.one + @api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity', + 'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id', + 'invoice_id.date_invoice', 'invoice_id.date') + def _compute_price(self): + currency = self.invoice_id and self.invoice_id.currency_id or None + price = self.price_unit * (1 - (self.discount or 0.0) / 100.0) + + ratio = 1.0 + qty_done_total = 0.0 + if self.invoice_id.type in ('out_invoice', 'out_refund'): + move_lines = self.sale_line_ids.mapped('move_ids.move_line_ids') + else: + move_lines = self.purchase_line_id.mapped('move_ids.move_line_ids') + for move_line in move_lines: + qty_done = move_line.qty_done + r = move_line.lot_id.catch_weight_ratio + ratio = ((ratio * qty_done_total) + (qty_done * r)) / (qty_done + qty_done_total) + qty_done_total += qty_done + price = price * ratio + + taxes = False + if self.invoice_line_tax_ids: + taxes = self.invoice_line_tax_ids.compute_all(price, currency, self.quantity, product=self.product_id, + partner=self.invoice_id.partner_id) + self.price_subtotal = price_subtotal_signed = taxes['total_excluded'] if taxes else self.quantity * price + self.price_total = taxes['total_included'] if taxes else self.price_subtotal + if self.invoice_id.currency_id and self.invoice_id.currency_id != self.invoice_id.company_id.currency_id: + price_subtotal_signed = self.invoice_id.currency_id.with_context( + date=self.invoice_id._get_currency_rate_date()).compute(price_subtotal_signed, + self.invoice_id.company_id.currency_id) + sign = self.invoice_id.type in ['in_refund', 'out_refund'] and -1 or 1 + self.price_subtotal_signed = price_subtotal_signed * sign \ No newline at end of file diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py new file mode 100644 index 00000000..6acec3a4 --- /dev/null +++ b/product_catch_weight/models/stock.py @@ -0,0 +1,20 @@ +from odoo import api, fields, models + + +class StockProductionLot(models.Model): + _inherit = 'stock.production.lot' + + catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) + + +class StockMoveLine(models.Model): + _inherit = 'stock.move.line' + + lot_catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) + lot_catch_weight_ratio_related = fields.Float(related='lot_id.catch_weight_ratio') + #lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio') + + # def _action_done(self): + # super(StockMoveLine, self)._action_done() + # for ml in self.filtered(lambda l: l.product_id.tracking == 'serial' and l.lot_id): + # ml.lot_id.catch_weight_ratio = ml.lot_catch_weight_ratio diff --git a/product_catch_weight/models/stock_patch.py b/product_catch_weight/models/stock_patch.py new file mode 100644 index 00000000..8787f4f6 --- /dev/null +++ b/product_catch_weight/models/stock_patch.py @@ -0,0 +1,114 @@ +from odoo import fields +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_round, float_compare, float_is_zero +from odoo.addons.stock.models.stock_move_line import StockMoveLine + + +def _action_done(self): + """ This method is called during a move's `action_done`. It'll actually move a quant from + the source location to the destination location, and unreserve if needed in the source + location. + + This method is intended to be called on all the move lines of a move. This method is not + intended to be called when editing a `done` move (that's what the override of `write` here + is done. + """ + + # First, we loop over all the move lines to do a preliminary check: `qty_done` should not + # be negative and, according to the presence of a picking type or a linked inventory + # adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink + # the line. It is mandatory in order to free the reservation and correctly apply + # `action_done` on the next move lines. + ml_to_delete = self.env['stock.move.line'] + for ml in self: + # Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`. + uom_qty = float_round(ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='HALF-UP') + precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure') + qty_done = float_round(ml.qty_done, precision_digits=precision_digits, rounding_method='HALF-UP') + if float_compare(uom_qty, qty_done, precision_digits=precision_digits) != 0: + raise UserError(_('The quantity done for the product "%s" doesn\'t respect the rounding precision \ + defined on the unit of measure "%s". Please change the quantity done or the \ + rounding precision of your unit of measure.') % ( + ml.product_id.display_name, ml.product_uom_id.name)) + + qty_done_float_compared = float_compare(ml.qty_done, 0, precision_rounding=ml.product_uom_id.rounding) + if qty_done_float_compared > 0: + if ml.product_id.tracking != 'none': + picking_type_id = ml.move_id.picking_type_id + if picking_type_id: + if picking_type_id.use_create_lots: + # If a picking type is linked, we may have to create a production lot on + # the fly before assigning it to the move line if the user checked both + # `use_create_lots` and `use_existing_lots`. + if ml.lot_name and not ml.lot_id: + lot = self.env['stock.production.lot'].create( + {'name': ml.lot_name, 'product_id': ml.product_id.id, 'catch_weight_ratio': ml.lot_catch_weight_ratio} + ) + ml.write({'lot_id': lot.id}) + elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots: + # If the user disabled both `use_create_lots` and `use_existing_lots` + # checkboxes on the picking type, he's allowed to enter tracked + # products without a `lot_id`. + continue + elif ml.move_id.inventory_id: + # If an inventory adjustment is linked, the user is allowed to enter + # tracked products without a `lot_id`. + continue + + if not ml.lot_id: + raise UserError(_('You need to supply a lot/serial number for %s.') % ml.product_id.name) + elif qty_done_float_compared < 0: + raise UserError(_('No negative quantities allowed')) + else: + ml_to_delete |= ml + ml_to_delete.unlink() + + # Now, we can actually move the quant. + done_ml = self.env['stock.move.line'] + for ml in self - ml_to_delete: + if ml.product_id.type == 'product': + Quant = self.env['stock.quant'] + rounding = ml.product_uom_id.rounding + + # if this move line is force assigned, unreserve elsewhere if needed + if not ml.location_id.should_bypass_reservation() and float_compare(ml.qty_done, ml.product_qty, + precision_rounding=rounding) > 0: + extra_qty = ml.qty_done - ml.product_qty + ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, + package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml) + # unreserve what's been reserved + if not ml.location_id.should_bypass_reservation() and ml.product_id.type == 'product' and ml.product_qty: + try: + Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, + package_id=ml.package_id, owner_id=ml.owner_id, strict=True) + except UserError: + Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, + package_id=ml.package_id, owner_id=ml.owner_id, strict=True) + + # move what's been actually done + quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, + rounding_method='HALF-UP') + available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, + lot_id=ml.lot_id, package_id=ml.package_id, + owner_id=ml.owner_id) + if available_qty < 0 and ml.lot_id: + # see if we can compensate the negative quants with some untracked quants + untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, + package_id=ml.package_id, owner_id=ml.owner_id, + strict=True) + if untracked_qty: + taken_from_untracked_qty = min(untracked_qty, abs(quantity)) + Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, + lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id) + Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, + lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) + Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, + package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date) + done_ml |= ml + # Reset the reserved quantity as we just moved it to the destination location. + (self - ml_to_delete).with_context(bypass_reservation_update=True).write({ + 'product_uom_qty': 0.00, + 'date': fields.Datetime.now(), + }) + +StockMoveLine._action_done = _action_done diff --git a/product_catch_weight/tests/__init__.py b/product_catch_weight/tests/__init__.py new file mode 100644 index 00000000..0edad729 --- /dev/null +++ b/product_catch_weight/tests/__init__.py @@ -0,0 +1 @@ +from . import test_catch_weight diff --git a/product_catch_weight/tests/test_catch_weight.py b/product_catch_weight/tests/test_catch_weight.py new file mode 100644 index 00000000..a167e530 --- /dev/null +++ b/product_catch_weight/tests/test_catch_weight.py @@ -0,0 +1,152 @@ +import logging +# from odoo.addons.stock.tests.test_move2 import TestPickShip +from odoo import fields +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestPicking(TransactionCase): + def setUp(self): + super(TestPicking, self).setUp() + self.partner1 = self.env.ref('base.res_partner_2') + self.product1 = self.env['product.product'].create({ + 'name': 'Product 1', + 'type': 'product', + 'tracking': 'serial', + 'list_price': 100.0, + 'standard_price': 50.0, + 'taxes_id': [(5, 0, 0)], + }) + #self.product1 = self.env.ref('product.product_order_01') + self.product1.write({ + 'type': 'product', + 'tracking': 'serial', + }) + self.stock_location = self.env.ref('stock.stock_location_stock') + + + # def test_creation(self): + # self.productA.tracking = 'serial' + # lot = self.env['stock.production.lot'].create({ + # 'product_id': self.productA.id, + # 'name': '123456789', + # }) + # + # lot.catch_weight_ratio = 0.8 + # _logger.warn(lot.xxxcatch_weight_ratio) + + + + # def test_delivery(self): + # self.productA.tracking = 'serial' + # picking_pick, picking_pack, picking_ship = self.create_pick_pack_ship() + # stock_location = self.env['stock.location'].browse(self.stock_location) + # lot = self.env['stock.production.lot'].create({ + # 'product_id': self.productA.id, + # 'name': '123456789', + # 'catch_weight_ratio': 0.8, + # }) + # self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot) + + def test_so_invoice(self): + ratio = 0.8 + lot = self.env['stock.production.lot'].create({ + 'product_id': self.product1.id, + 'name': '123456789', + 'catch_weight_ratio': ratio, + }) + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot) + so = self.env['sale.order'].create({ + 'partner_id': self.partner1.id, + 'partner_invoice_id': self.partner1.id, + 'partner_shipping_id': self.partner1.id, + 'order_line': [(0, 0, {'product_id': self.product1.id})], + }) + so.action_confirm() + self.assertTrue(so.state in ('sale', 'done')) + self.assertEqual(len(so.picking_ids), 1) + picking = so.picking_ids + self.assertEqual(picking.state, 'assigned') + self.assertEqual(picking.move_lines.move_line_ids.lot_id, lot) + picking.move_lines.move_line_ids.qty_done = 1.0 + picking.button_validate() + self.assertEqual(picking.state, 'done') + + inv_id = so.action_invoice_create() + inv = self.env['account.invoice'].browse(inv_id) + self.assertEqual(inv.amount_total, ratio * self.product1.list_price) + + def test_so_invoice2(self): + ratio1 = 0.8 + ratio2 = 1.1 + lot1 = self.env['stock.production.lot'].create({ + 'product_id': self.product1.id, + 'name': '1-low', + 'catch_weight_ratio': ratio1, + }) + lot2 = self.env['stock.production.lot'].create({ + 'product_id': self.product1.id, + 'name': '1-high', + 'catch_weight_ratio': ratio2, + }) + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1) + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2) + so = self.env['sale.order'].create({ + 'partner_id': self.partner1.id, + 'partner_invoice_id': self.partner1.id, + 'partner_shipping_id': self.partner1.id, + 'order_line': [(0, 0, {'product_id': self.product1.id, 'product_uom_qty': 2.0})], + }) + so.action_confirm() + self.assertTrue(so.state in ('sale', 'done')) + self.assertEqual(len(so.picking_ids), 1) + picking = so.picking_ids + self.assertEqual(picking.state, 'assigned') + self.assertEqual(picking.move_lines.move_line_ids.mapped('lot_id'), lot1 + lot2) + for line in picking.move_lines.move_line_ids: + line.qty_done = 1.0 + picking.button_validate() + self.assertEqual(picking.state, 'done') + + inv_id = so.action_invoice_create() + inv = self.env['account.invoice'].browse(inv_id) + self.assertEqual(inv.amount_total, (ratio1 * self.product1.list_price) + (ratio2 * self.product1.list_price)) + + def test_po_invoice(self): + ratio1 = 0.8 + ratio2 = 1.1 + ratios = (ratio1, ratio2) + price = self.product1.standard_price + po = self.env['purchase.order'].create({ + 'partner_id': self.partner1.id, + 'order_line': [(0, 0, { + 'product_id': self.product1.id, + 'product_qty': 2.0, + 'name': 'Test', + 'date_planned': fields.Datetime.now(), + 'product_uom': self.product1.uom_po_id.id, + 'price_unit': price, + })] + }) + po.button_confirm() + self.assertEqual(po.state, 'purchase') + self.assertEqual(len(po.picking_ids), 1) + + picking = po.picking_ids + for i, line in enumerate(picking.move_lines.move_line_ids): + line.write({'lot_name': str(i), 'qty_done': 1.0, 'lot_catch_weight_ratio': ratios[i]}) + picking.button_validate() + self.assertEqual(picking.state, 'done') + + inv = self.env['account.invoice'].create({ + 'type': 'in_invoice', + 'partner_id': self.partner1.id, + 'purchase_id': po.id, + }) + inv.purchase_order_change() + self.assertEqual(len(inv.invoice_line_ids), 1) + self.assertEqual(inv.invoice_line_ids.quantity, 2.0) + self.assertEqual(inv.amount_total, (ratio1 * price) + (ratio2 * price)) + + diff --git a/product_catch_weight/views/stock_views.xml b/product_catch_weight/views/stock_views.xml new file mode 100644 index 00000000..cee50753 --- /dev/null +++ b/product_catch_weight/views/stock_views.xml @@ -0,0 +1,35 @@ + + + + stock.production.lot.form.inherit + stock.production.lot + + + + + + + + + + stock.move.line.form.inherit + stock.move.line + + + + + + + + + stock.move.line.operations.tree.inherit + stock.move.line + + + + + + + + + \ No newline at end of file From b1c4aba75e082e486563b74ff334502907654833 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Sun, 10 Jun 2018 11:02:27 -0700 Subject: [PATCH 19/36] Improve UI, allowing user to measure the catch weight in a specific unit of measure. This UOM should be convertable (in the same category) as the normal stock UOM for the product. Example: If you want to sell 'units' of 50lbs then you should make a "50lbs" UOM in the Weight category and use that as the sale and purchase UOM, then your "Catch Weight UOM" can be the stock "lb(s)" UOM. --- product_catch_weight/__manifest__.py | 1 + product_catch_weight/models/__init__.py | 1 + .../models/account_invoice.py | 6 ++ product_catch_weight/models/product.py | 7 ++ product_catch_weight/models/stock.py | 44 +++++++++--- product_catch_weight/models/stock_patch.py | 3 +- .../tests/test_catch_weight.py | 46 +++++++------ .../views/account_invoice_views.xml | 69 +++++++++++++++++++ product_catch_weight/views/stock_views.xml | 43 +++++++++--- 9 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 product_catch_weight/models/product.py create mode 100644 product_catch_weight/views/account_invoice_views.xml diff --git a/product_catch_weight/__manifest__.py b/product_catch_weight/__manifest__.py index 7c2ef68c..76d3cea2 100644 --- a/product_catch_weight/__manifest__.py +++ b/product_catch_weight/__manifest__.py @@ -12,6 +12,7 @@ 'license': 'AGPL-3', 'website': 'https://hibou.io/', 'data': [ + 'views/account_invoice_views.xml', 'views/stock_views.xml', ], 'installable': True, diff --git a/product_catch_weight/models/__init__.py b/product_catch_weight/models/__init__.py index 57a87093..5e099bc5 100644 --- a/product_catch_weight/models/__init__.py +++ b/product_catch_weight/models/__init__.py @@ -1,3 +1,4 @@ from . import account_invoice +from . import product from . import stock_patch from . import stock diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py index ab72943c..ee8c9f92 100644 --- a/product_catch_weight/models/account_invoice.py +++ b/product_catch_weight/models/account_invoice.py @@ -7,6 +7,9 @@ _logger = logging.getLogger(__name__) class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' + catch_weight = fields.Float(string='Catch Weight', digits=(10, 4), compute='_compute_price', store=True) + catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + @api.one @api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity', 'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id', @@ -17,6 +20,7 @@ class AccountInvoiceLine(models.Model): ratio = 1.0 qty_done_total = 0.0 + catch_weight = 0.0 if self.invoice_id.type in ('out_invoice', 'out_refund'): move_lines = self.sale_line_ids.mapped('move_ids.move_line_ids') else: @@ -26,7 +30,9 @@ class AccountInvoiceLine(models.Model): r = move_line.lot_id.catch_weight_ratio ratio = ((ratio * qty_done_total) + (qty_done * r)) / (qty_done + qty_done_total) qty_done_total += qty_done + catch_weight += move_line.lot_id.catch_weight price = price * ratio + self.catch_weight = catch_weight taxes = False if self.invoice_line_tax_ids: diff --git a/product_catch_weight/models/product.py b/product_catch_weight/models/product.py new file mode 100644 index 00000000..16cb4b91 --- /dev/null +++ b/product_catch_weight/models/product.py @@ -0,0 +1,7 @@ +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _inherit = 'product.template' + + catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py index 6acec3a4..1ad5d3b6 100644 --- a/product_catch_weight/models/stock.py +++ b/product_catch_weight/models/stock.py @@ -4,17 +4,43 @@ from odoo import api, fields, models class StockProductionLot(models.Model): _inherit = 'stock.production.lot' - catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) + catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), compute='_compute_catch_weight_ratio') + catch_weight = fields.Float(string='Catch Weight', digits=(10, 4)) + catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + + + @api.depends('catch_weight') + def _compute_catch_weight_ratio(self): + for lot in self: + if not lot.catch_weight_uom_id: + lot.catch_weight_ratio = 1.0 + else: + lot.catch_weight_ratio = lot.catch_weight_uom_id._compute_quantity(lot.catch_weight, + lot.product_id.uom_id, + rounding_method='DOWN') + + +class StockMove(models.Model): + _inherit = 'stock.move' + + product_catch_weight_uom_id = fields.Many2one('product.uom', related="product_id.catch_weight_uom_id") + + def _prepare_move_line_vals(self, quantity=None, reserved_quant=None): + vals = super(StockMove, self)._prepare_move_line_vals(quantity=quantity, reserved_quant=reserved_quant) + vals['catch_weight_uom_id'] = self.product_catch_weight_uom_id.id if self.product_catch_weight_uom_id else False + return vals + + def action_show_details(self): + action = super(StockMove, self).action_show_details() + action['context']['show_catch_weight'] = bool(self.product_id.catch_weight_uom_id) + return action class StockMoveLine(models.Model): _inherit = 'stock.move.line' - lot_catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) - lot_catch_weight_ratio_related = fields.Float(related='lot_id.catch_weight_ratio') - #lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio') - - # def _action_done(self): - # super(StockMoveLine, self)._action_done() - # for ml in self.filtered(lambda l: l.product_id.tracking == 'serial' and l.lot_id): - # ml.lot_id.catch_weight_ratio = ml.lot_catch_weight_ratio + catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) + catch_weight = fields.Float(string='Catch Weight', digits=(10,4)) + catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') + lot_catch_weight = fields.Float(related='lot_id.catch_weight') + lot_catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') diff --git a/product_catch_weight/models/stock_patch.py b/product_catch_weight/models/stock_patch.py index 8787f4f6..fca04e4f 100644 --- a/product_catch_weight/models/stock_patch.py +++ b/product_catch_weight/models/stock_patch.py @@ -41,8 +41,9 @@ def _action_done(self): # the fly before assigning it to the move line if the user checked both # `use_create_lots` and `use_existing_lots`. if ml.lot_name and not ml.lot_id: + lot_catch_weight = ml.catch_weight_uom_id._compute_quantity(ml.catch_weight, ml.product_id.catch_weight_uom_id, rounding_method='DOWN') lot = self.env['stock.production.lot'].create( - {'name': ml.lot_name, 'product_id': ml.product_id.id, 'catch_weight_ratio': ml.lot_catch_weight_ratio} + {'name': ml.lot_name, 'product_id': ml.product_id.id, 'catch_weight': lot_catch_weight} ) ml.write({'lot_id': lot.id}) elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots: diff --git a/product_catch_weight/tests/test_catch_weight.py b/product_catch_weight/tests/test_catch_weight.py index a167e530..1b13cb55 100644 --- a/product_catch_weight/tests/test_catch_weight.py +++ b/product_catch_weight/tests/test_catch_weight.py @@ -9,7 +9,16 @@ _logger = logging.getLogger(__name__) class TestPicking(TransactionCase): def setUp(self): super(TestPicking, self).setUp() + self.nominal_weight = 50.0 self.partner1 = self.env.ref('base.res_partner_2') + self.stock_location = self.env.ref('stock.stock_location_stock') + self.ref_uom_id = self.env.ref('product.product_uom_kgm') + self.product_uom_id = self.env['product.uom'].create({ + 'name': '50 ref', + 'category_id': self.ref_uom_id.category_id.id, + 'uom_type': 'bigger', + 'factor_inv': self.nominal_weight, + }) self.product1 = self.env['product.product'].create({ 'name': 'Product 1', 'type': 'product', @@ -17,13 +26,10 @@ class TestPicking(TransactionCase): 'list_price': 100.0, 'standard_price': 50.0, 'taxes_id': [(5, 0, 0)], + 'uom_id': self.product_uom_id.id, + 'uom_po_id': self.product_uom_id.id, + 'catch_weight_uom_id': self.ref_uom_id.id, }) - #self.product1 = self.env.ref('product.product_order_01') - self.product1.write({ - 'type': 'product', - 'tracking': 'serial', - }) - self.stock_location = self.env.ref('stock.stock_location_stock') # def test_creation(self): @@ -50,12 +56,13 @@ class TestPicking(TransactionCase): # self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot) def test_so_invoice(self): - ratio = 0.8 + ref_weight = 45.0 lot = self.env['stock.production.lot'].create({ 'product_id': self.product1.id, 'name': '123456789', - 'catch_weight_ratio': ratio, + 'catch_weight': ref_weight, }) + self.assertAlmostEqual(lot.catch_weight_ratio, ref_weight / self.nominal_weight) self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot) so = self.env['sale.order'].create({ 'partner_id': self.partner1.id, @@ -75,20 +82,20 @@ class TestPicking(TransactionCase): inv_id = so.action_invoice_create() inv = self.env['account.invoice'].browse(inv_id) - self.assertEqual(inv.amount_total, ratio * self.product1.list_price) + self.assertAlmostEqual(inv.amount_total, lot.catch_weight_ratio * self.product1.list_price) def test_so_invoice2(self): - ratio1 = 0.8 - ratio2 = 1.1 + ref_weight1 = 45.0 + ref_weight2 = 51.0 lot1 = self.env['stock.production.lot'].create({ 'product_id': self.product1.id, 'name': '1-low', - 'catch_weight_ratio': ratio1, + 'catch_weight': ref_weight1, }) lot2 = self.env['stock.production.lot'].create({ 'product_id': self.product1.id, 'name': '1-high', - 'catch_weight_ratio': ratio2, + 'catch_weight': ref_weight2, }) self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1) self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2) @@ -111,12 +118,12 @@ class TestPicking(TransactionCase): inv_id = so.action_invoice_create() inv = self.env['account.invoice'].browse(inv_id) - self.assertEqual(inv.amount_total, (ratio1 * self.product1.list_price) + (ratio2 * self.product1.list_price)) + self.assertAlmostEqual(inv.amount_total, self.product1.list_price * (lot1.catch_weight_ratio + lot2.catch_weight_ratio)) def test_po_invoice(self): - ratio1 = 0.8 - ratio2 = 1.1 - ratios = (ratio1, ratio2) + ref_weight1 = 45.0 + ref_weight2 = 51.0 + weights = (ref_weight1, ref_weight2) price = self.product1.standard_price po = self.env['purchase.order'].create({ 'partner_id': self.partner1.id, @@ -135,7 +142,7 @@ class TestPicking(TransactionCase): picking = po.picking_ids for i, line in enumerate(picking.move_lines.move_line_ids): - line.write({'lot_name': str(i), 'qty_done': 1.0, 'lot_catch_weight_ratio': ratios[i]}) + line.write({'lot_name': str(i), 'qty_done': 1.0, 'catch_weight': weights[i]}) picking.button_validate() self.assertEqual(picking.state, 'done') @@ -147,6 +154,5 @@ class TestPicking(TransactionCase): inv.purchase_order_change() self.assertEqual(len(inv.invoice_line_ids), 1) self.assertEqual(inv.invoice_line_ids.quantity, 2.0) - self.assertEqual(inv.amount_total, (ratio1 * price) + (ratio2 * price)) - + self.assertAlmostEqual(inv.amount_total, price * sum(w / self.nominal_weight for w in weights)) diff --git a/product_catch_weight/views/account_invoice_views.xml b/product_catch_weight/views/account_invoice_views.xml new file mode 100644 index 00000000..8eb57740 --- /dev/null +++ b/product_catch_weight/views/account_invoice_views.xml @@ -0,0 +1,69 @@ + + + + account.invoice.form.inherit + account.invoice + + + + + + + + + + account.invoice.supplier.form.inherit + account.invoice + + + + + + + + + + + + \ No newline at end of file diff --git a/product_catch_weight/views/stock_views.xml b/product_catch_weight/views/stock_views.xml index cee50753..40e2d9dd 100644 --- a/product_catch_weight/views/stock_views.xml +++ b/product_catch_weight/views/stock_views.xml @@ -7,17 +7,32 @@ + + - - stock.move.line.form.inherit - stock.move.line - + + + + + + + + + + + + stock.move.operations.form.inherit + stock.move + - - + + + + + {'tree_view_ref': 'stock.view_stock_move_line_operation_tree', 'default_product_uom_id': product_uom, 'default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_catch_weight_uom_id': product_catch_weight_uom_id} @@ -27,8 +42,20 @@ - - + + + + + + + + + product.template.common.form.inherit + product.template + + + + From 8c059303bdf3cbd76251343e44c332be9f5c3d96 Mon Sep 17 00:00:00 2001 From: Kristen Marie Kulha Date: Fri, 14 Sep 2018 11:05:35 -0700 Subject: [PATCH 20/36] Added computed field `has_catch_weight` to `stock.picking` model in `product_catch_weight` module. Additionally, added filter and group by for `has_catch_weight` to stock.picking search view. --- .../models/account_invoice.py | 6 ++++- product_catch_weight/models/stock.py | 12 ++++++++- product_catch_weight/views/stock_views.xml | 26 ++++++++++++------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py index ee8c9f92..c6b3db66 100644 --- a/product_catch_weight/models/account_invoice.py +++ b/product_catch_weight/models/account_invoice.py @@ -27,8 +27,12 @@ class AccountInvoiceLine(models.Model): move_lines = self.purchase_line_id.mapped('move_ids.move_line_ids') for move_line in move_lines: qty_done = move_line.qty_done + current_qty_done = qty_done + qty_done_total r = move_line.lot_id.catch_weight_ratio - ratio = ((ratio * qty_done_total) + (qty_done * r)) / (qty_done + qty_done_total) + if current_qty_done == 0: + ratio = 0 + else: + ratio = ((ratio * qty_done_total) + (qty_done * r)) / current_qty_done qty_done_total += qty_done catch_weight += move_line.lot_id.catch_weight price = price * ratio diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py index 1ad5d3b6..c5cc9363 100644 --- a/product_catch_weight/models/stock.py +++ b/product_catch_weight/models/stock.py @@ -8,7 +8,6 @@ class StockProductionLot(models.Model): catch_weight = fields.Float(string='Catch Weight', digits=(10, 4)) catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') - @api.depends('catch_weight') def _compute_catch_weight_ratio(self): for lot in self: @@ -44,3 +43,14 @@ class StockMoveLine(models.Model): catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') lot_catch_weight = fields.Float(related='lot_id.catch_weight') lot_catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + has_catch_weight = fields.Boolean(string="Has Catch Weight", compute='_compute_has_catch_weight', store=True) + + @api.depends('move_lines.product_catch_weight_uom_id') + def _compute_has_catch_weight(self): + for picking in self: + picking.has_catch_weight = any(picking.mapped('move_lines.product_catch_weight_uom_id')) diff --git a/product_catch_weight/views/stock_views.xml b/product_catch_weight/views/stock_views.xml index 40e2d9dd..3ce9015d 100644 --- a/product_catch_weight/views/stock_views.xml +++ b/product_catch_weight/views/stock_views.xml @@ -13,16 +13,6 @@ - - - - - - - - - - stock.move.operations.form.inherit stock.move @@ -36,6 +26,7 @@ + stock.move.line.operations.tree.inherit stock.move.line @@ -49,6 +40,7 @@ + product.template.common.form.inherit product.template @@ -59,4 +51,18 @@ + + + stock.view.picking.internal.search.inherit + stock.picking + + + + + + + + + + \ No newline at end of file From 90add5bfdc70159822ed8f15303b5574aeda013a Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 16 Oct 2018 11:40:31 -0700 Subject: [PATCH 21/36] FIX `product_catch_weight` if line moved doesn't have a lot, then the ratio is implicitly 0 despite having a default ratio. --- product_catch_weight/models/account_invoice.py | 2 +- product_catch_weight/tests/test_catch_weight.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py index c6b3db66..11879f5b 100644 --- a/product_catch_weight/models/account_invoice.py +++ b/product_catch_weight/models/account_invoice.py @@ -25,7 +25,7 @@ class AccountInvoiceLine(models.Model): move_lines = self.sale_line_ids.mapped('move_ids.move_line_ids') else: move_lines = self.purchase_line_id.mapped('move_ids.move_line_ids') - for move_line in move_lines: + for move_line in move_lines.filtered(lambda l: l.lot_id): qty_done = move_line.qty_done current_qty_done = qty_done + qty_done_total r = move_line.lot_id.catch_weight_ratio diff --git a/product_catch_weight/tests/test_catch_weight.py b/product_catch_weight/tests/test_catch_weight.py index 1b13cb55..48821d7a 100644 --- a/product_catch_weight/tests/test_catch_weight.py +++ b/product_catch_weight/tests/test_catch_weight.py @@ -30,6 +30,7 @@ class TestPicking(TransactionCase): 'uom_po_id': self.product_uom_id.id, 'catch_weight_uom_id': self.ref_uom_id.id, }) + self.pricelist = self.env.ref('product.list0') # def test_creation(self): @@ -69,6 +70,7 @@ class TestPicking(TransactionCase): 'partner_invoice_id': self.partner1.id, 'partner_shipping_id': self.partner1.id, 'order_line': [(0, 0, {'product_id': self.product1.id})], + 'pricelist_id': self.pricelist.id, }) so.action_confirm() self.assertTrue(so.state in ('sale', 'done')) @@ -104,6 +106,7 @@ class TestPicking(TransactionCase): 'partner_invoice_id': self.partner1.id, 'partner_shipping_id': self.partner1.id, 'order_line': [(0, 0, {'product_id': self.product1.id, 'product_uom_qty': 2.0})], + 'pricelist_id': self.pricelist.id, }) so.action_confirm() self.assertTrue(so.state in ('sale', 'done')) From 0fa7905590acfa229fc301a747b6aec0e969ff8b Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 23 Nov 2018 09:31:47 -0800 Subject: [PATCH 22/36] IMP `product_catch_weight` Add related fields to `stock.quant` for tree view and POS. --- product_catch_weight/models/stock.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py index c5cc9363..0e553569 100644 --- a/product_catch_weight/models/stock.py +++ b/product_catch_weight/models/stock.py @@ -54,3 +54,11 @@ class StockPicking(models.Model): def _compute_has_catch_weight(self): for picking in self: picking.has_catch_weight = any(picking.mapped('move_lines.product_catch_weight_uom_id')) + + +class StockQuant(models.Model): + _inherit = 'stock.quant' + + lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio') + lot_catch_weight = fields.Float(related='lot_id.catch_weight') + lot_catch_weight_uom_id = fields.Many2one('product.uom', related='lot_id.catch_weight_uom_id') From c4c309246ad9fc4d486d5171e649f3b458b3a9e5 Mon Sep 17 00:00:00 2001 From: Bhoomi Date: Fri, 20 Sep 2019 16:36:54 -0400 Subject: [PATCH 23/36] MIG `product_catch_weight` For Odoo 12.0 --- product_catch_weight/__manifest__.py | 2 +- .../models/account_invoice.py | 2 +- product_catch_weight/models/product.py | 2 +- product_catch_weight/models/stock.py | 10 ++++---- .../tests/test_catch_weight.py | 4 +-- .../views/account_invoice_views.xml | 25 +++++++++---------- product_catch_weight/views/stock_views.xml | 7 +++--- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/product_catch_weight/__manifest__.py b/product_catch_weight/__manifest__.py index 76d3cea2..535582f7 100644 --- a/product_catch_weight/__manifest__.py +++ b/product_catch_weight/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Product Catch Weight', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', 'category': 'Warehouse', 'depends': [ 'sale_stock', diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py index 11879f5b..c6db156c 100644 --- a/product_catch_weight/models/account_invoice.py +++ b/product_catch_weight/models/account_invoice.py @@ -8,7 +8,7 @@ class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' catch_weight = fields.Float(string='Catch Weight', digits=(10, 4), compute='_compute_price', store=True) - catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + catch_weight_uom_id = fields.Many2one('uom.uom', related='product_id.catch_weight_uom_id') @api.one @api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity', diff --git a/product_catch_weight/models/product.py b/product_catch_weight/models/product.py index 16cb4b91..0232c1c8 100644 --- a/product_catch_weight/models/product.py +++ b/product_catch_weight/models/product.py @@ -4,4 +4,4 @@ from odoo import api, fields, models class ProductProduct(models.Model): _inherit = 'product.template' - catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') + catch_weight_uom_id = fields.Many2one('uom.uom', string='Catch Weight UOM') diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py index 0e553569..b96fa5bc 100644 --- a/product_catch_weight/models/stock.py +++ b/product_catch_weight/models/stock.py @@ -6,7 +6,7 @@ class StockProductionLot(models.Model): catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), compute='_compute_catch_weight_ratio') catch_weight = fields.Float(string='Catch Weight', digits=(10, 4)) - catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + catch_weight_uom_id = fields.Many2one('uom.uom', related='product_id.catch_weight_uom_id') @api.depends('catch_weight') def _compute_catch_weight_ratio(self): @@ -22,7 +22,7 @@ class StockProductionLot(models.Model): class StockMove(models.Model): _inherit = 'stock.move' - product_catch_weight_uom_id = fields.Many2one('product.uom', related="product_id.catch_weight_uom_id") + product_catch_weight_uom_id = fields.Many2one('uom.uom', related="product_id.catch_weight_uom_id") def _prepare_move_line_vals(self, quantity=None, reserved_quant=None): vals = super(StockMove, self)._prepare_move_line_vals(quantity=quantity, reserved_quant=reserved_quant) @@ -40,9 +40,9 @@ class StockMoveLine(models.Model): catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) catch_weight = fields.Float(string='Catch Weight', digits=(10,4)) - catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') + catch_weight_uom_id = fields.Many2one('uom.uom', string='Catch Weight UOM') lot_catch_weight = fields.Float(related='lot_id.catch_weight') - lot_catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + lot_catch_weight_uom_id = fields.Many2one('uom.uom', related='product_id.catch_weight_uom_id') class StockPicking(models.Model): @@ -61,4 +61,4 @@ class StockQuant(models.Model): lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio') lot_catch_weight = fields.Float(related='lot_id.catch_weight') - lot_catch_weight_uom_id = fields.Many2one('product.uom', related='lot_id.catch_weight_uom_id') + lot_catch_weight_uom_id = fields.Many2one('uom.uom', related='lot_id.catch_weight_uom_id') diff --git a/product_catch_weight/tests/test_catch_weight.py b/product_catch_weight/tests/test_catch_weight.py index 48821d7a..9a383ea3 100644 --- a/product_catch_weight/tests/test_catch_weight.py +++ b/product_catch_weight/tests/test_catch_weight.py @@ -12,8 +12,8 @@ class TestPicking(TransactionCase): self.nominal_weight = 50.0 self.partner1 = self.env.ref('base.res_partner_2') self.stock_location = self.env.ref('stock.stock_location_stock') - self.ref_uom_id = self.env.ref('product.product_uom_kgm') - self.product_uom_id = self.env['product.uom'].create({ + self.ref_uom_id = self.env.ref('uom.product_uom_kgm') + self.product_uom_id = self.env['uom.uom'].create({ 'name': '50 ref', 'category_id': self.ref_uom_id.category_id.id, 'uom_type': 'bigger', diff --git a/product_catch_weight/views/account_invoice_views.xml b/product_catch_weight/views/account_invoice_views.xml index 8eb57740..695a08e2 100644 --- a/product_catch_weight/views/account_invoice_views.xml +++ b/product_catch_weight/views/account_invoice_views.xml @@ -25,21 +25,21 @@