diff --git a/l10n_us_ar_hr_payroll/__init__.py b/l10n_us_ar_hr_payroll/__init__.py deleted file mode 100755 index 0650744f..00000000 --- a/l10n_us_ar_hr_payroll/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/l10n_us_ar_hr_payroll/__manifest__.py b/l10n_us_ar_hr_payroll/__manifest__.py deleted file mode 100755 index 45f86c6b..00000000 --- a/l10n_us_ar_hr_payroll/__manifest__.py +++ /dev/null @@ -1,28 +0,0 @@ -{ - 'name': 'USA - Arkansas - Payroll', - 'author': 'Hibou Corp. ', - 'license': 'AGPL-3', - 'category': 'Localization', - 'depends': ['l10n_us_hr_payroll'], - 'version': '12.0.2019.0.0', - 'description': """ -USA - Arkansas Payroll Rules. -================================== - -* 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 - """, - '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 deleted file mode 100755 index ce1b73cc..00000000 --- a/l10n_us_ar_hr_payroll/data/base.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - 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 deleted file mode 100755 index 6bbf8f32..00000000 --- a/l10n_us_ar_hr_payroll/data/final.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - 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 deleted file mode 100755 index 00c3b202..00000000 --- a/l10n_us_ar_hr_payroll/data/rates.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - 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 deleted file mode 100755 index 71b77a79..00000000 --- a/l10n_us_ar_hr_payroll/data/rules.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - Wage: US-AR Unemployment - WAGE_US_AR_UNEMP - python - result = (contract.futa_type != contract.FUTA_TYPE_BASIC) - code - -rate = payslip.dict.get_rate('US_AR_UNEMP') -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 -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) - 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_ar4ec_texarkana_exemption or contract.ar_ar4ec_tax_exempt) - code - -wages = categories.GROSS -annual_gross_pay = 0.00 -allowance_amt = contract.ar_ar4ec_allowances * 26.00 -schedule_pay = contract.schedule_pay -standard_deduction = 2200 -additional_withholding = contract.ar_ar4ec_additional_wh - -if contract.w4_filing_status == 'married': - standard_deduction = standard_deduction * 2 - -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') - -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 deleted file mode 100644 index e99aa24a..00000000 --- a/l10n_us_ar_hr_payroll/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100755 index 2bb52b2c..00000000 --- a/l10n_us_ar_hr_payroll/models/hr_payroll.py +++ /dev/null @@ -1,14 +0,0 @@ -from odoo import models, fields, api - - -class USARHrContract(models.Model): - _inherit = 'hr.contract' - - 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') diff --git a/l10n_us_ar_hr_payroll/tests/__init__.py b/l10n_us_ar_hr_payroll/tests/__init__.py deleted file mode 100755 index 7d2ca3e4..00000000 --- a/l10n_us_ar_hr_payroll/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100755 index 3f72292f..00000000 --- a/l10n_us_ar_hr_payroll/tests/test_us_ar_payslip_2019.py +++ /dev/null @@ -1,291 +0,0 @@ -from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip - - -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) - - # 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) - - 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 - # 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() - 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 - tax_exempt = 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_ar4ec_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['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) - - 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_ar4ec_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), 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) - - 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_ar4ec_additional_wh = 0.0 - contract.ar_ar4ec_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_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) - - 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_ar4ec_additional_wh = 0.0 - contract.ar_ar4ec_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_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) - - 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 # 10000.00 monthly is over 50,000 annually. - 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_ar4ec_additional_wh = 0.0 - contract.ar_ar4ec_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_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) - - 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_ar4ec_additional_wh = additional_wh - contract.ar_ar4ec_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_ar4ec_additional_wh = 0 - contract.ar_ar4ec_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_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) - - 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 deleted file mode 100755 index e3fc69be..00000000 --- a/l10n_us_ar_hr_payroll/views/hr_payroll_views.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - hr.contract.form.inherit - hr.contract - 106 - - - - - - - - - - - - - - - - diff --git a/l10n_us_ia_hr_payroll/__init__.py b/l10n_us_ia_hr_payroll/__init__.py deleted file mode 100755 index 0650744f..00000000 --- a/l10n_us_ia_hr_payroll/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/l10n_us_ia_hr_payroll/__manifest__.py b/l10n_us_ia_hr_payroll/__manifest__.py deleted file mode 100755 index 7cc7b1d0..00000000 --- a/l10n_us_ia_hr_payroll/__manifest__.py +++ /dev/null @@ -1,29 +0,0 @@ -{ - 'name': 'USA - Iowa - Payroll', - 'author': 'Hibou Corp. ', - 'license': 'AGPL-3', - 'category': 'Localization', - 'depends': ['l10n_us_hr_payroll'], - 'version': '12.0.2019.0.0', - 'description': """ -USA - Iowa Payroll Rules. -================================== - -* 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 - """, - - '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 deleted file mode 100755 index 6e5a2391..00000000 --- a/l10n_us_ia_hr_payroll/data/base.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - 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 deleted file mode 100755 index 58b73b5a..00000000 --- a/l10n_us_ia_hr_payroll/data/final.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - 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 deleted file mode 100755 index 6c910f0c..00000000 --- a/l10n_us_ia_hr_payroll/data/rates.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - 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 deleted file mode 100755 index 62ec8a59..00000000 --- a/l10n_us_ia_hr_payroll/data/rules.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - 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 = 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 -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': 1.67, - '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 deleted file mode 100644 index e99aa24a..00000000 --- a/l10n_us_ia_hr_payroll/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100755 index 7eac3055..00000000 --- a/l10n_us_ia_hr_payroll/models/hr_payroll.py +++ /dev/null @@ -1,9 +0,0 @@ -from odoo import models, fields, api - - -class USIAHrContract(models.Model): - _inherit = 'hr.contract' - - 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/__init__.py b/l10n_us_ia_hr_payroll/tests/__init__.py deleted file mode 100755 index 4f771ce0..00000000 --- a/l10n_us_ia_hr_payroll/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100755 index bde22b29..00000000 --- a/l10n_us_ia_hr_payroll/tests/test_us_ia_payslip.py +++ /dev/null @@ -1,182 +0,0 @@ -from odoo.addons.l10n_us_hr_payroll.tests.test_us_payslip import TestUsPayslip, process_payslip - - -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 - 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 - - 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'] - # 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 - # 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 - # 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) - # 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 - - 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 - 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 - - 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 - - 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_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'), - 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['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) - - - - - diff --git a/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml b/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml deleted file mode 100755 index 4b822364..00000000 --- a/l10n_us_ia_hr_payroll/views/hr_payroll_views.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - hr.contract.form.inherit - hr.contract - 119 - - - - - - - - - - - - - - - - diff --git a/pos_pax/__init__.py b/pos_pax/__init__.py new file mode 100644 index 00000000..09434554 --- /dev/null +++ b/pos_pax/__init__.py @@ -0,0 +1,3 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import models diff --git a/pos_pax/__manifest__.py b/pos_pax/__manifest__.py new file mode 100644 index 00000000..a99e72e8 --- /dev/null +++ b/pos_pax/__manifest__.py @@ -0,0 +1,43 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +{ + 'name': 'POS PAX Terminal Credit Card', + 'author': 'Hibou Corp. ', + 'version': '12.0.1.0.0', + 'category': 'Point of Sale', + 'sequence': 6, + 'summary': 'PAX Terminal Credit card support for Point Of Sale', + 'description': """ +Allow credit card POS payments +============================== + +This module allows customers to pay for their orders with credit cards. +The transactions are processed on the PAX Terminal (no credit credit card +information through Odoo itself). + +Depending on Device and processor support, this integration can handle: + +* Magnetic swiped cards +* EMV chip cards +* Contactless (including Apple Pay, Samsung Pay, Google Pay) + + """, + 'depends': [ + 'web', + 'pos_sale', + # 'hibou_professional', + ], + 'website': 'https://hibou.io', + 'data': [ + 'views/pos_pax_templates.xml', + 'views/pos_pax_views.xml', + 'views/pos_config_setting_views.xml', + ], + 'demo': [ + ], + 'qweb': [ + 'static/src/xml/pos_pax.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/pos_pax/models/__init__.py b/pos_pax/models/__init__.py new file mode 100644 index 00000000..c25936ee --- /dev/null +++ b/pos_pax/models/__init__.py @@ -0,0 +1,4 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import pos_pax +from . import update diff --git a/pos_pax/models/pos_pax.py b/pos_pax/models/pos_pax.py new file mode 100644 index 00000000..acfe3e54 --- /dev/null +++ b/pos_pax/models/pos_pax.py @@ -0,0 +1,61 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import models, fields, api, _ +from odoo.tools.float_utils import float_compare + + +class AccountBankStatementLine(models.Model): + _inherit = "account.bank.statement.line" + + pax_card_number = fields.Char(string='Card Number', help='Masked credit card.') + pax_txn_id = fields.Char(string='PAX Transaction ID') + + +class AccountJournal(models.Model): + _inherit = 'account.journal' + + pos_use_pax = fields.Boolean(string='Use POS PAX Terminal', + help='When used in POS, communicate with PAX Terminal for transactions.') + + +class PosConfig(models.Model): + _inherit = 'pos.config' + + pax_endpoint = fields.Char(string='PAX Endpoint', + help='Endpoint for PAX device (include protocol (http or https) and port). ' + 'e.g. http://192.168.1.101:10009') + + +class PosOrder(models.Model): + _inherit = "pos.order" + + @api.model + def _payment_fields(self, ui_paymentline): + fields = super(PosOrder, self)._payment_fields(ui_paymentline) + + fields.update({ + 'pax_card_number': ui_paymentline.get('pax_card_number'), + 'pax_txn_id': ui_paymentline.get('pax_txn_id'), + }) + + return fields + + def add_payment(self, data): + statement_id = super(PosOrder, self).add_payment(data) + statement_lines = self.env['account.bank.statement.line'].search([('statement_id', '=', statement_id), + ('pos_statement_id', '=', self.id), + ('journal_id', '=', data['journal'])]) + statement_lines = statement_lines.filtered(lambda line: float_compare(line.amount, data['amount'], + precision_rounding=line.journal_currency_id.rounding) == 0) + + # we can get multiple statement_lines when there are >1 credit + # card payments with the same amount. In that case it doesn't + # matter which statement line we pick, just pick one that + # isn't already used. + for line in statement_lines: + if not line.pax_card_number: + line.pax_card_number = data.get('pax_card_number') + line.pax_txn_id = data.get('pax_txn_id') + break + + return statement_id diff --git a/pos_pax/models/update.py b/pos_pax/models/update.py new file mode 100644 index 00000000..6a08ab50 --- /dev/null +++ b/pos_pax/models/update.py @@ -0,0 +1,20 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import models + + +class PublisherWarrantyContract(models.AbstractModel): + _inherit = 'publisher_warranty.contract' + + def _get_hibou_modules(self): + modules = super(PublisherWarrantyContract, self)._get_hibou_modules() + try: + self.env.cr.execute( + 'SELECT COUNT(*) FROM pos_config WHERE pax_endpoint != \'\' AND pax_endpoint IS NOT NULL') + pax_count = self.env.cr.fetchone()[0] or 0 + modules.update({ + 'pos_pax': pax_count, + }) + except: + pass + return modules diff --git a/pos_pax/static/src/css/pos_pax.css b/pos_pax/static/src/css/pos_pax.css new file mode 100644 index 00000000..6b64ca39 --- /dev/null +++ b/pos_pax/static/src/css/pos_pax.css @@ -0,0 +1,17 @@ +.pos .paymentline.selected.o_pos_pax_txn_pending, .pos .paymentline.o_pos_pax_txn_pending { + background: rgb(239, 153, 65); +} +.pos .col-tendered.edit.o_pos_pax_txn_pending { + color: rgb(239, 153, 65); + box-shadow: 0px 0px 0px 3px rgb(239, 153, 65); +} + +.pos .paymentline .pax_send_transaction { + cursor: pointer !important; + border: 1px solid rgba(255, 255, 255, 0.5); + background-color: rgba(255,255,255, 0.2); + display: block; + text-align: center; + margin: 6px 0 0 0; + padding: 2px 5px; +} diff --git a/pos_pax/static/src/js/jquery_base64.js b/pos_pax/static/src/js/jquery_base64.js new file mode 100644 index 00000000..a9666d94 --- /dev/null +++ b/pos_pax/static/src/js/jquery_base64.js @@ -0,0 +1,133 @@ +odoo.define('pos_pax.jquery_base64', function (require) { + "use strict"; + + // Hibou Corp. © 2020 - Wrapped library for PAX Device + + var jQuery = require('jquery'); + + /*! + * jquery.base64.js 0.1 - https://github.com/yckart/jquery.base64.js + * Makes Base64 en & -decoding simpler as it is. + * + * Based upon: https://gist.github.com/Yaffle/1284012 + * + * Copyright (c) 2012 Yannick Albert (http://yckart.com) + * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php). + * 2013/02/10 + **/ + ;(function ($) { + + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + a256 = '', + r64 = [256], + r256 = [256], + i = 0; + + var UTF8 = { + + /** + * Encode multi-byte Unicode string into utf-8 multiple single-byte characters + * (BMP / basic multilingual plane only) + * + * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars + * + * @param {String} strUni Unicode string to be encoded as UTF-8 + * @returns {String} encoded string + */ + encode: function (strUni) { + // use regular expressions & String.replace callback function for better efficiency + // than procedural approaches + var strUtf = strUni.replace(/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz + function (c) { + var cc = c.charCodeAt(0); + return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f); + }) + .replace(/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz + function (c) { + var cc = c.charCodeAt(0); + return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f); + }); + return strUtf; + }, + + /** + * Decode utf-8 encoded string back into multi-byte Unicode characters + * + * @param {String} strUtf UTF-8 string to be decoded back to Unicode + * @returns {String} decoded string + */ + decode: function (strUtf) { + // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char! + var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars + function (c) { // (note parentheses for precence) + var cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f); + return String.fromCharCode(cc); + }) + .replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars + function (c) { // (note parentheses for precence) + var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f; + return String.fromCharCode(cc); + }); + return strUni; + } + }; + + while (i < 256) { + var c = String.fromCharCode(i); + a256 += c; + r256[i] = i; + r64[i] = b64.indexOf(c); + ++i; + } + + function code(s, discard, alpha, beta, w1, w2) { + s = String(s); + var buffer = 0, + i = 0, + length = s.length, + result = '', + bitsInBuffer = 0; + + while (i < length) { + var c = s.charCodeAt(i); + c = c < 256 ? alpha[c] : -1; + + buffer = (buffer << w1) + c; + bitsInBuffer += w1; + + while (bitsInBuffer >= w2) { + bitsInBuffer -= w2; + var tmp = buffer >> bitsInBuffer; + result += beta.charAt(tmp); + buffer ^= tmp << bitsInBuffer; + } + ++i; + } + if (!discard && bitsInBuffer > 0) result += beta.charAt(buffer << (w2 - bitsInBuffer)); + return result; + } + + var Plugin = $.base64 = function (dir, input, encode) { + return input ? Plugin[dir](input, encode) : dir ? null : this; + }; + + Plugin.btoa = Plugin.encode = function (plain, utf8encode) { + plain = Plugin.raw === false || Plugin.utf8encode || utf8encode ? UTF8.encode(plain) : plain; + plain = code(plain, false, r256, b64, 8, 6); + return plain + '===='.slice((plain.length % 4) || 4); + }; + + Plugin.atob = Plugin.decode = function (coded, utf8decode) { + coded = String(coded).split('='); + var i = coded.length; + do { + --i; + coded[i] = code(coded[i], true, r64, a256, 6, 8); + } while (i > 0); + coded = coded.join(''); + return Plugin.raw === false || Plugin.utf8decode || utf8decode ? UTF8.decode(coded) : coded; + }; + }(jQuery)); + + return jQuery; +}); diff --git a/pos_pax/static/src/js/pax_device.js b/pos_pax/static/src/js/pax_device.js new file mode 100644 index 00000000..14879313 --- /dev/null +++ b/pos_pax/static/src/js/pax_device.js @@ -0,0 +1,479 @@ +odoo.define('pos_pax.pax_device', function (require) { + "use strict"; + + var $ = require('pos_pax.jquery_base64'); + + // Hibou Corp. © 2020 - Wrapped library for PAX Device + // Additions and Fixes: + // 2020-11-12 Fixed variable i and ii in getLRC + // 2020-11-12 Fixed/added 'fail' mechanisms for XHR + + //HEX TO BASE64 + function hexToBase64(str) { + return $.base64.btoa(String.fromCharCode.apply(null, + str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")) + ); + } + + //BASE64 TO HEX + function base64ToHex(str) { + for (var i = 0, bin = $.base64.atob(str), hex = []; i < bin.length; ++i) { + var tmp = bin.charCodeAt(i).toString(16); + if (tmp.length === 1) tmp = "0" + tmp; + hex[hex.length] = tmp; + } + return hex.join(" "); + } + + function StringToHex(response){ + var responseHex = ""; + for(var i=0; i0)?String.fromCharCode(lrc):0; + }, + + + + //Connect to the server + HttpCommunication : function(commandType,url,callback,timeout,fail){ + var xhr = null; + if(window.XMLHttpRequest) { + xhr = new XMLHttpRequest(); + } else { + try{ + xhr = new ActiveXObject('Microsoft.XMLHttp'); + }catch(e){ + xhr = new ActiveXObject('msxml2.xmlhttp'); + } + } + //get + + xhr.open("GET", url,true); + xhr.onreadystatechange=function(){ + if(xhr.readyState==4) { + //alert(xhr.status); + if(xhr.status==200) { + var response = xhr.responseText; + console.log("Raw response: "+response); + + var checkParams = StringToHex(response).split(" ").pop(); + var RedundancyCheck = StringToHex(response).split(" ").pop().substring(1); + + var check = PAX.getLRC(checkParams); + + if(check == RedundancyCheck){ + //get package detail info + var packetInfo = []; + var len = StringToHex(response).indexOf("03"); + var hex = StringToHex(response).slice(0,len).split(/02|1c/); + + console.log(hex); + if(commandType == "DoCredit"){ + var subHex=[], subPacketInfo=[]; + for(var i=0; i0){ + subHex = hex[i].split("1f"); + console.log(subHex); + subPacketInfo = []; + for(var j=0; j"+customData+""); + // } + + + var url = this.mDestinationIP + '?' + final_b64; + console.log("URL: " + url); + + this.HttpCommunication('DoCredit',url,function(response){ + callback(response); + },PAX.timeout.DoCredit,fail); + } + }; + + return PAX; + +}); \ No newline at end of file diff --git a/pos_pax/static/src/js/pos_pax.js b/pos_pax/static/src/js/pos_pax.js new file mode 100644 index 00000000..905c0a2e --- /dev/null +++ b/pos_pax/static/src/js/pos_pax.js @@ -0,0 +1,349 @@ +odoo.define('pos_pax.pos_pax', function (require) { +"use strict"; + +// Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +var core = require('web.core'); +var screens = require('point_of_sale.screens'); +var gui = require('point_of_sale.gui'); +var pos_model = require('point_of_sale.models'); +var _t = core._t; +var PopupWidget = require('point_of_sale.popups'); +var PaymentScreenWidget = screens.PaymentScreenWidget; +var PAX = require('pos_pax.pax_device'); + +PAX.mDestinationIP = ''; + +pos_model.load_fields("account.journal", "pos_use_pax"); + +pos_model.PosModel = pos_model.PosModel.extend({ + + getPAXOnlinePaymentJournals: function () { + var self = this; + var online_payment_journals = []; + + $.each(this.journals, function (i, val) { + if (val.pos_use_pax) { + online_payment_journals.push({label:self.getCashRegisterByJournalID(val.id).journal_id[1], item:val.id}); + } + }); + return online_payment_journals; + }, + + getCashRegisterByJournalID: function (journal_id) { + var cashregister_return = false; + + $.each(this.cashregisters, function (index, cashregister) { + if (cashregister.journal_id[0] === journal_id) { + cashregister_return = cashregister; + } + }); + + return cashregister_return; + }, + + decodePAXResponse: function (data) { + console.log(data); + if (data[5] == 'ABORTED') { + console.log('aborted'); + return {fail: 'Transaction Aborted'}; + } else if (data[5] == 'OK') { + return { + success: true, + approval: data[6][2], + txn_id: data[10][0], + card_num: '***' + data[9][0], + } + } + return {fail: 'Unknown Response. ' + data}; + } +}); + +var _paylineproto = pos_model.Paymentline.prototype; +pos_model.Paymentline = pos_model.Paymentline.extend({ + init_from_JSON: function (json) { + _paylineproto.init_from_JSON.apply(this, arguments); + + this.paid = true; + this.pax_txn_pending = json.pax_txn_pending; + this.pax_card_number = json.pax_card_number; + this.pax_approval = json.pax_approval; + this.pax_txn_id = json.pax_txn_id; + + this.set_credit_card_name(); + }, + export_as_JSON: function () { + return _.extend(_paylineproto.export_as_JSON.apply(this, arguments), { + paid: this.paid, + pax_txn_pending: this.pax_txn_pending, + pax_card_number: this.pax_card_number, + pax_approval: this.pax_approval, + pax_txn_id: this.pax_txn_id, + }); + }, + set_credit_card_name: function () { + if (this.pax_card_number) { + this.name = this.pax_card_number; + } + } +}); + +// Popup to show all transaction state for the payment. +var PAXPaymentTransactionPopupWidget = PopupWidget.extend({ + template: 'PAXPaymentTransactionPopupWidget', + show: function (options) { + var self = this; + this._super(options); + options.transaction.then(function (data) { + if (data.auto_close) { + setTimeout(function () { + self.gui.close_popup(); + }, 2000); + } else { + self.close(); + self.$el.find('.popup').append(''); + } + + self.$el.find('p.body').html(data.message); + }).progress(function (data) { + self.$el.find('p.body').html(data.message); + }); + } +}); +gui.define_popup({name:'pax-payment-transaction', widget: PAXPaymentTransactionPopupWidget}); + +PaymentScreenWidget.include({ + + _get_pax_txn_pending_line: function () { + var i = 0; + var lines = this.pos.get_order().get_paymentlines(); + + for (i = 0; i < lines.length; i++) { + if (lines[i].pax_txn_pending) { + return lines[i]; + } + } + + return 0; + }, + + // Handler to manage the card reader string + pax_credit_transaction: function (old_deferred, retry_nr) { + var order = this.pos.get_order(); + + if(this.pos.getPAXOnlinePaymentJournals().length === 0) { + return; + } + + var self = this; + var transaction = {}; + var pending_line = self._get_pax_txn_pending_line(); + if ( ! pending_line ) { + console.log('no pending line found!'); + return; + } + + var purchase_amount = pending_line.get_amount(); + + var transactionType = '01'; // SALE + if (purchase_amount < 0.0) { + purchase_amount = -purchase_amount; + transactionType = '02'; // RETURN + } + + transaction = { + command: 'T00', + version: '1.28', + transactionType: transactionType, + amountInformation: { + TransactionAmount: (purchase_amount * 100) | 0, // cast to integer + }, + cashierInformation: { + ClerkID: this.pos.user.name, + }, + traceInformation: { + ReferenceNumber: self.pos.get_order().uid, + // InvoiceNumber: self.pos.get_order().uid, + }, + } + + var def = old_deferred || new $.Deferred(); + retry_nr = retry_nr || 0; + + // show the transaction popup. + // the transaction deferred is used to update transaction status + // if we have a previous deferred it indicates that this is a retry + if (! old_deferred) { + self.gui.show_popup('pax-payment-transaction', { + transaction: def + }); + def.notify({ + message: _t('Handling transaction...'), + }); + } + + PAX.mDestinationIP = this.pos.config.pax_endpoint; + PAX.DoCredit(transaction, function (response) { + console.log(response); + var parsed_response = self.pos.decodePAXResponse(response); + if (parsed_response.fail) { + def.resolve({message: parsed_response.fail}) + return; + } + if (parsed_response.success) { + pending_line.paid = true; + pending_line.pax_card_number = parsed_response.card_num; + pending_line.pax_txn_id = parsed_response.txn_id; + pending_line.pax_approval = parsed_response.approval; + pending_line.pax_txn_pending = false; + pending_line.set_credit_card_name(); + self.order_changes(); + self.reset_input(); + self.render_paymentlines(); + order.trigger('change', order); + def.resolve({message: 'Approval ' + parsed_response.approval, auto_close: true}) + } + def.resolve({message: _t('Unknown response.')}) + }, function (fail){ + def.resolve({message: _t('Communication Failure: ') + fail}); + }); + }, + + remove_paymentline_by_ref: function (line) { + this.pos.get_order().remove_paymentline(line); + this.reset_input(); + this.render_paymentlines(); + }, + + pax_do_reversal: function (line) { + var def = new $.Deferred(); + var self = this; + var transaction = {}; + + // show the transaction popup. + // the transaction deferred is used to update transaction status + this.gui.show_popup('pax-payment-transaction', { + transaction: def + }); + def.notify({ + message: 'Sending reversal...', + }); + + transaction = { + command: 'T00', + version: '1.28', + transactionType: (line.get_amount() > 0) ? '17' : '18', // V/SALE, V/RETURN + cashierInformation: { + ClerkID: this.pos.user.name, + }, + traceInformation: { + ReferenceNumber: self.pos.get_order().uid, + InvoiceNumber: '', + AuthCode: line.pax_approval, + TransactionNumber: line.pax_txn_id, + }, + } + + PAX.mDestinationIP = this.pos.config.pax_endpoint; + PAX.DoCredit(transaction, function (response) { + var parsed_response = self.pos.decodePAXResponse(response); + if (parsed_response.fail) { + def.resolve({message: parsed_response.fail}) + return; + } + if (parsed_response.success) { + def.resolve({ + message: _t('Reversal succeeded.'), + auto_close: true, + }); + self.remove_paymentline_by_ref(line); + return; + } + def.resolve({message: _t('Unknown response.')}) + }, function (fail){ + def.resolve({message: _t('Communication Failure: ') + fail}); + }); + }, + + click_delete_paymentline: function (cid) { + var lines = this.pos.get_order().get_paymentlines(); + + for (var i = 0; i < lines.length; i++) { + if (lines[i].cid === cid && lines[i].pax_txn_id) { + this.pax_do_reversal(lines[i]); + return; + } + } + + this._super(cid); + }, + + // make sure there is only one paymentline waiting for a swipe + click_paymentmethods: function (id) { + var order = this.pos.get_order(); + var cashregister = null; + for (var i = 0; i < this.pos.cashregisters.length; i++) { + if (this.pos.cashregisters[i].journal_id[0] === id){ + cashregister = this.pos.cashregisters[i]; + break; + } + } + + if (cashregister.journal.pos_use_pax) { + var pending_line = this._get_pax_txn_pending_line(); + + if (pending_line) { + this.gui.show_popup('error',{ + 'title': _t('Error'), + 'body': _t('One credit card txn already pending.'), + }); + } else { + this._super(id); + order.selected_paymentline.pax_txn_pending = true; + this.render_paymentlines(); + order.trigger('change', order); // needed so that export_to_JSON gets triggered + } + } else { + this._super(id); + } + }, + + click_pax_send_transaction: function (id) { + var pending_txn_line = this._get_pax_txn_pending_line(); + if (!pending_txn_line) { + this.gui.show_popup('error',{ + 'title': _t('Error'), + 'body': _t('No pending payment line to send.'), + }); + return; + } + this.pax_credit_transaction(); + }, + + render_paymentlines: function() { + this._super(); + var self = this; + self.$('.paymentlines-container').on('click', '.pax_send_transaction', function(){ + self.click_pax_send_transaction(); + }); + }, + + // before validating, get rid of any paymentlines that are pending + validate_order: function(force_validation) { + if (this.pos.get_order().is_paid() && ! this.invoicing) { + var lines = this.pos.get_order().get_paymentlines(); + + for (var i = 0; i < lines.length; i++) { + if (lines[i].pax_txn_pending) { + this.pos.get_order().remove_paymentline(lines[i]); + this.render_paymentlines(); + } + } + } + + this._super(force_validation); + }, + +}); + +}); diff --git a/pos_pax/static/src/xml/pos_pax.xml b/pos_pax/static/src/xml/pos_pax.xml new file mode 100644 index 00000000..3126f3b5 --- /dev/null +++ b/pos_pax/static/src/xml/pos_pax.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + +
WAITING FOR TXN
+
+ + + + + Send + + + + + + + + this.removeAttr('class'); + this.attr('t-attf-class', 'paymentline selected #{line.pax_txn_pending ? \'o_pos_pax_txn_pending\' : \'\'}'); + + + this.removeAttr('class'); + this.attr('t-attf-class', 'paymentline #{line.pax_txn_pending ? \'o_pos_pax_txn_pending\' : \'\'}'); + + + this.removeAttr('class'); + this.attr('t-attf-class', 'col-tendered edit #{line.pax_txn_pending ? \'o_pos_pax_txn_pending\' : \'\'}'); + +
+ + + + +
+
CARDHOLDER WILL PAY CARD ISSUER
+
ABOVE AMOUNT PURSUANT
+
TO CARDHOLDER AGREEMENT
+
+
+
X______________________________
+ + +
+
+ + + + + +
  APPROVAL CODE:
+
+
+
+ +
+ +
+
+
+ + + + + + + &nbsp;&nbsp;APPROVAL CODE: + + + + + +
+ +
+
+
+
diff --git a/pos_pax/views/pos_config_setting_views.xml b/pos_pax/views/pos_config_setting_views.xml new file mode 100644 index 00000000..1c978d58 --- /dev/null +++ b/pos_pax/views/pos_config_setting_views.xml @@ -0,0 +1,23 @@ + + + + + pos.config.form.inherit.pax + pos.config + + + +
+
+ PAX +
+

Your PAX Device Endpoint

+ +
+
+
+
+
+
+ +
diff --git a/pos_pax/views/pos_pax_templates.xml b/pos_pax/views/pos_pax_templates.xml new file mode 100644 index 00000000..b49d526c --- /dev/null +++ b/pos_pax/views/pos_pax_templates.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/pos_pax/views/pos_pax_views.xml b/pos_pax/views/pos_pax_views.xml new file mode 100644 index 00000000..a8ff0cff --- /dev/null +++ b/pos_pax/views/pos_pax_views.xml @@ -0,0 +1,40 @@ + + + + + POS Journal + account.journal + + + + + + + + + + + + account.bank.journal.form.inherited.pos.pax + account.journal + + + + + + + + + + POS orders + pos.order + + + + + + + + + +