Merge branch 'new/12.0/pos_pax' into 12.0-test

This commit is contained in:
Jared Kipe
2020-11-21 06:52:28 -08:00
35 changed files with 1275 additions and 1035 deletions

View File

@@ -1 +0,0 @@
from . import models

View File

@@ -1,28 +0,0 @@
{
'name': 'USA - Arkansas - Payroll',
'author': 'Hibou Corp. <hello@hibou.io>',
'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
}

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- PARTNERS -->
<record id="res_partner_ar_dws_unemp" model="res.partner">
<field name="name">Arkansas Department of Workforce Solutions - Unemployment Tax</field>
<field name="supplier">1</field>
<field eval="0" name="customer"/>
</record>
<record id="res_partner_ar_dfa_withhold" model="res.partner">
<field name="name">Arkansas Department of Financial Administration - Income Tax Withholding</field>
<field name="supplier">1</field>
<field eval="0" name="customer"/>
</record>
<!-- CONTRIBUTION REGISTERS -->
<record id="contrib_register_ar_dws_unemp" model="hr.contribution.register">
<field name="name">Arkansas Unemployment</field>
<field name="note">Arkansas Department of Workforce Solutions - Unemployment</field>
<field name="partner_id" ref="res_partner_ar_dws_unemp"/>
</record>
<record id="contrib_register_ar_dfa_withhold" model="hr.contribution.register">
<field name="name">Arkansas Income Tax Withholding</field>
<field name="note">Arkansas Department of Financial Administration - Income Tax Withholding</field>
<field name="partner_id" ref="res_partner_ar_dfa_withhold"/>
</record>
<!-- HR SALARY RULE CATEGORIES-->
<record id="hr_payroll_ar_unemp_wages" model="hr.salary.rule.category">
<field name="name">Wage: US-AR Unemployment</field>
<field name="code">WAGE_US_AR_UNEMP</field>
</record>
<record id="hr_payroll_ar_unemp" model="hr.salary.rule.category">
<field name="name">ER: US-AR Unemployment</field>
<field name="code">ER_US_AR_UNEMP</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<record id="hr_payroll_ar_income_withhold" model="hr.salary.rule.category">
<field name="name">EE: US-AR Income Tax Withholding</field>
<field name="code">EE_US_AR_INC_WITHHOLD</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
</odoo>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- HR PAYROLL STRUCTURE -->
<record id="hr_payroll_salary_structure_us_ar_employee" model="hr.payroll.structure">
<field name="code">US_AR_EMP</field>
<field name="name">USA Arkansas Employee</field>
<field eval="[(6, 0, [
ref('hr_payroll_rules_ar_unemp_wages'),
ref('hr_payroll_rules_ar_unemp'),
ref('hr_payroll_rules_ar_inc_withhold'),
])]" name="rule_ids"/>
<field name="company_id" ref="base.main_company"/>
<field name="parent_id" ref="l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee"/>
</record>
</odoo>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="hr_payroll_rates_ar_unemp" model="hr.payroll.rate">
<field name="name">US AR Unemployment</field>
<field name="code">US_AR_UNEMP</field>
<field name="rate">3.2</field>
<field name="date_from">2019-01-01</field>
<field name="wage_limit_year" eval="10000.00"/>
</record>
</data>
</odoo>

View File

@@ -1,113 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_payroll_rules_ar_unemp_wages" model="hr.salary.rule">
<field name="sequence" eval="423"/>
<field name="category_id" ref="hr_payroll_ar_unemp_wages"/>
<field name="name">Wage: US-AR Unemployment</field>
<field name="code">WAGE_US_AR_UNEMP</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (contract.futa_type != contract.FUTA_TYPE_BASIC)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rules_ar_unemp" model="hr.salary.rule">
<field name="sequence" eval="443"/>
<field name="category_id" ref="hr_payroll_ar_unemp"/>
<field name="name">ER: US-AR Unemployment</field>
<field name="code">ER_US_AR_UNEMP</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (contract.futa_type != contract.FUTA_TYPE_BASIC)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
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
</field>
<field name="register_id" ref="contrib_register_ar_dws_unemp"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rules_ar_inc_withhold" model="hr.salary.rule">
<field name="sequence" eval="155"/>
<field name="category_id" ref="hr_payroll_ar_income_withhold"/>
<field name="name">EE: US-AR Income Tax Withholding</field>
<field name="code">EE_US_AR_INC_WITHHOLD</field>
<field name="condition_select">python</field>
<field name="condition_python">result = not (contract.ar_ar4ec_texarkana_exemption or contract.ar_ar4ec_tax_exempt)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
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 &lt; 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 &lt;= net_taxable_income:
taxed = cap - last
result = result + (taxed * (rate / 100.0))
last = cap
elif cap &gt; net_taxable_income:
taxed = net_taxable_income - last
result = result + (taxed * (rate / 100.0))
break
result = (result / pay_period) + additional_withholding
result = -result
</field>
<field name="register_id" ref="contrib_register_ar_dfa_withhold"/>
</record>
</odoo>

View File

@@ -1 +0,0 @@
from . import hr_payroll

View File

@@ -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')

View File

@@ -1 +0,0 @@
from . import test_us_ar_payslip_2019

View File

@@ -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)

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="hr_contract_form_l10n_us_ar_inherit" model="ir.ui.view">
<field name="name">hr.contract.form.inherit</field>
<field name="model">hr.contract</field>
<field name="priority">106</field>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//group[@name='state_filing']" position="inside">
<group string="Arkansas" name="ar">
<field name="ar_ar4ec_allowances" string="Allowances"/>
<field name="ar_ar4ec_additional_wh" string="Additional Withholding"/>
<field name="ar_ar4ec_tax_exempt" string="Tax Exempt"/>
<field name="ar_ar4ec_texarkana_exemption" string="Texarkana Exemption"/>
</group>
</xpath>
</data>
</field>
</record>
</data>
</odoo>

View File

@@ -1 +0,0 @@
from . import models

View File

@@ -1,29 +0,0 @@
{
'name': 'USA - Iowa - Payroll',
'author': 'Hibou Corp. <hello@hibou.io>',
'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
}

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- PARTNERS -->
<record id="res_partner_ia_wd_unemp" model="res.partner">
<field name="name">Iowa Workforce Development- Unemployment Tax</field>
<field name="supplier">1</field>
<field eval="0" name="customer"/>
</record>
<record id="res_partner_ia_dor_withhold" model="res.partner">
<field name="name">Iowa Department of Revenue - Income Tax Withholding</field>
<field name="supplier">1</field>
<field eval="0" name="customer"/>
</record>
<!-- CONTRIBUTION REGISTERS -->
<record id="contrib_register_ia_wd_unemp" model="hr.contribution.register">
<field name="name">Iowa Unemployment</field>
<field name="note">Iowa Workforce Development - Unemployment</field>
<field name="partner_id" ref="res_partner_ia_wd_unemp"/>
</record>
<record id="contrib_register_ia_dor_withhold" model="hr.contribution.register">
<field name="name">Iowa Income Tax Withholding</field>
<field name="note">Iowa Department of Revenue - Income Tax Withholding</field>
<field name="partner_id" ref="res_partner_ia_dor_withhold"/>
</record>
<!-- HR SALARY RULE CATEGORIES-->
<record id="hr_payroll_ia_unemp_wages" model="hr.salary.rule.category">
<field name="name">Wage: US-IA Unemployment</field>
<field name="code">WAGE_US_IA_UNEMP</field>
</record>
<record id="hr_payroll_ia_unemp" model="hr.salary.rule.category">
<field name="name">ER: US-IA Unemployment</field>
<field name="code">ER_US_IA_UNEMP</field>
<field name="parent_id" ref="hr_payroll.COMP"/>
</record>
<record id="hr_payroll_ia_income_withhold" model="hr.salary.rule.category">
<field name="name">EE: US-IA Income Tax Withholding</field>
<field name="code">EE_US_IA_INC_WITHHOLD</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
</odoo>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- HR PAYROLL STRUCTURE -->
<record id="hr_payroll_salary_structure_us_ia_employee" model="hr.payroll.structure">
<field name="code">US_IA_EMP</field>
<field name="name">USA Iowa Employee</field>
<field eval="[(6, 0, [
ref('hr_payroll_rules_ia_unemp_wages'),
ref('hr_payroll_rules_ia_unemp'),
ref('hr_payroll_rules_ia_inc_withhold'),
])]" name="rule_ids"/>
<field name="company_id" ref="base.main_company"/>
<field name="parent_id" ref="l10n_us_hr_payroll.hr_payroll_salary_structure_us_employee"/>
</record>
</odoo>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="hr_payroll_rates_ia_unemp" model="hr.payroll.rate">
<field name="name">US IA Unemployment</field>
<field name="code">US_IA_UNEMP</field>
<field name="rate">1.0</field>
<field name="date_from">2019-01-01</field>
<field name="wage_limit_year" eval="30600.00"/>
</record>
</data>
</odoo>

View File

@@ -1,160 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- HR SALARY RULES-->
<record id="hr_payroll_rules_ia_unemp_wages" model="hr.salary.rule">
<field name="sequence" eval="423"/>
<field name="category_id" ref="hr_payroll_ia_unemp_wages"/>
<field name="name">Wage: US-IA Unemployment</field>
<field name="code">WAGE_US_IA_UNEMP</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (contract.futa_type != contract.FUTA_TYPE_BASIC)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
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 &lt;= 0.0:
result = 0
elif remaining &lt; categories.BASIC:
result = remaining
else:
result = categories.BASIC
</field>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rules_ia_unemp" model="hr.salary.rule">
<field name="sequence" eval="443"/>
<field name="category_id" ref="hr_payroll_ia_unemp"/>
<field name="name">ER: US-IA Unemployment</field>
<field name="code">ER_US_IA_UNEMP</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (contract.futa_type != contract.FUTA_TYPE_BASIC)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
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
</field>
<field name="register_id" ref="contrib_register_ia_wd_unemp"/>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_payroll_rules_ia_inc_withhold" model="hr.salary.rule">
<field name="sequence" eval="155"/>
<field name="category_id" ref="hr_payroll_ia_income_withhold"/>
<field name="name">EE: US-IA Income Tax Withholding</field>
<field name="code">EE_US_IA_INC_WITHHOLD</field>
<field name="condition_select">python</field>
<field name="condition_python">result = not contract.ia_w4_tax_exempt</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
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 &lt; 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 &gt; 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
</field>
<field name="register_id" ref="contrib_register_ia_dor_withhold"/>
</record>
</odoo>

View File

@@ -1 +0,0 @@
from . import hr_payroll

View File

@@ -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")

View File

@@ -1 +0,0 @@
from . import test_us_ia_payslip

View File

@@ -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)

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="hr_contract_form_l10n_us_ia_inherit" model="ir.ui.view">
<field name="name">hr.contract.form.inherit</field>
<field name="model">hr.contract</field>
<field name="priority">119</field>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//group[@name='state_filing']" position="inside">
<group string="Iowa" name="ia">
<field name="ia_w4_allowances" string="Allowances"/>
<field name="ia_w4_additional_wh" string="Additional Withholding"/>
<field name="ia_w4_tax_exempt" string="Tax Exempt"/>
</group>
</xpath>
</data>
</field>
</record>
</data>
</odoo>

3
pos_pax/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import models

43
pos_pax/__manifest__.py Normal file
View File

@@ -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. <hello@hibou.io>',
'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,
}

View File

@@ -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

61
pos_pax/models/pos_pax.py Normal file
View File

@@ -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

20
pos_pax/models/update.py Normal file
View File

@@ -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

View File

@@ -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;
}

133
pos_pax/static/src/js/jquery_base64.js vendored Normal file
View File

@@ -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;
});

View File

@@ -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; i<response.length; i++){
if(responseHex == "")
responseHex = response.charCodeAt(i).toString(16).length<2?'0'+response.charCodeAt(i).toString(16):response.charCodeAt(i).toString(16);
else
responseHex += response.charCodeAt(i).toString(16).length<2?" " + '0'+response.charCodeAt(i).toString(16):" " + response.charCodeAt(i).toString(16);
}
return responseHex;
}
function HexToString(response){
var responseHex = "";
var arr = response.split(" ");
for(var i=0; i<arr.length; i++){
if(arr[i] == "")
continue;
responseHex += String.fromCharCode(parseInt(arr[i],16));
}
return responseHex;
}
var PAX = {
//IP of the POS
mDestinationIP : "http://127.0.0.1:10009", // - OLD "http://192.167.2.100:10009"; //http://112.199.49.146:8181
mStx : {
hex: 0x02,
code: "02"
},
mFS : {
hex : 0x1c,
code : "1c"
},
mEtx : {
hex : 0x03,
code : "03"
},
mUS : {
hex : 0x1F,
code : "1F"
},
//var _this : this;
customData : '',
timeout : {
"Initialize":120*1000,
"GetSignature":120*1000,
"DoSignature":120*1000,
"DoCredit":120*1000
},
//Set ip and port
Settings : function(ip,port){
this.mDestinationIP = "http://" + ip + ":" + port;
console.log("New service address: "+this.mDestinationIP);
},
AjaxTimeOut : function(command,timeout){
this.timeout[command]= timeout;
},
SetCustomData : function(custom_data){
this.customData = custom_data;
console.log(custom_data);
},
//Get LRC
getLRC : function(params){
var lrc = 0;
for(var i=1; i< params.length; i++){
var type_of = typeof(params[i]);
if(type_of == "string"){
var element = params[i].split("");
for(var ii=0; ii<element.length; ii++){
lrc ^= element[ii].charCodeAt(0);
}
}else{
lrc ^= params[i];
}
}
return (lrc>0)?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; i<hex.length; i++){
if(hex[i] != ""){
if(hex[i].indexOf("1f")>0){
subHex = hex[i].split("1f");
console.log(subHex);
subPacketInfo = [];
for(var j=0; j<subHex.length; j++){
if(subHex[j]!=''){
subPacketInfo.push(HexToString(subHex[j]));
}
}
console.log(subPacketInfo);
packetInfo.push(subPacketInfo);
}else{
packetInfo[i] = HexToString(hex[i]);
}
}
}
}else{
for(var i=0; i<hex.length; i++){
if(hex[i] != ""){
packetInfo[i] = HexToString(hex[i]);
}
}
}
console.log("Separate package info: ");
console.log(packetInfo);
callback(packetInfo);
}
} else {
if(fail) {
fail(xhr.status);
}
}
}
};
xhr.send(null);
},
Initialize : function(initialInfo,callback,fail){
var params = [this.mStx.hex,initialInfo.command,this.mFS.hex, initialInfo.version, this.mEtx.hex];
//[02]A08[1c]1.28[1c]0[1c]90000[03]
//var params = [0x02,"A08",0x1c,"1.28",0x1c, "0", 0x1c,"90000",0x03];
var lrc = this.getLRC(params);
var command_hex = base64ToHex($.base64.btoa(initialInfo.command));
var version_hex = base64ToHex($.base64.btoa(initialInfo.version));
//var elements = [this.mStx, command_hex, this.mFS, version_hex, this.mEtx, base64ToHex($.base64.btoa(lrc))];
var elements = [this.mStx.code, command_hex, this.mFS.code, version_hex, this.mEtx.code, base64ToHex($.base64.btoa(lrc))];
var final_string = elements.join(" ");
//console.log("final_string: " + final_string);
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('Initialize',url,function(response){
callback(response);
},PAX.timeout.Initialize,fail);
},
//GET SIGNATURE
GetSignature : function(getSignatureInfo,callback,fail){
var params = [this.mStx.hex,getSignatureInfo.command,this.mFS.hex,getSignatureInfo.version,this.mFS.hex, getSignatureInfo.offset, this.mFS.hex,getSignatureInfo.requestlength,this.mEtx.hex];
var lrc = this.getLRC(params);
//prepare for base64 encoding.
var command_hex = base64ToHex($.base64.btoa(getSignatureInfo.command));
var version_hex = base64ToHex($.base64.btoa(getSignatureInfo.version));
var offset_hex = base64ToHex($.base64.btoa(getSignatureInfo.offset));
var requestlength_hex = base64ToHex($.base64.btoa(getSignatureInfo.requestlength));
//var elements = [this.mStx.code, command_hex, this.mFS.code, version_hex, this.mFS.code, offset_hex, this.mFS.code, requestlength_hex, this.mEtx.code, base64ToHex($.base64.btoa(lrc))];
var elements = [this.mStx.code];
elements.push(command_hex);
elements.push(this.mFS.code);
elements.push(version_hex);
elements.push(this.mFS.code);
if(offset_hex != ''){
elements.push(offset_hex);
}
elements.push(this.mFS.code);
if(requestlength_hex != ''){
elements.push(requestlength_hex);
}
elements.push(this.mEtx.code);
elements.push(base64ToHex($.base64.btoa(lrc)));
var final_string = elements.join(" ");
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('GetSignature',url,function(response){
callback(response);
},PAX.timeout.GetSignature,fail);
},
//DO SIGNATURE
DoSignature : function(doSignatureInfo,callback,fail){
var params = [this.mStx.hex,doSignatureInfo.command, this.mFS.hex, doSignatureInfo.version, this.mFS.hex, doSignatureInfo.uploadFlag, this.mFS.hex,doSignatureInfo.hostReferenceNumber, this.mFS.hex, doSignatureInfo.edcType, this.mFS.hex, doSignatureInfo.timeout, this.mEtx.hex];
var lrc = this.getLRC(params);
//prepare for base64 encoding.
var command_hex = base64ToHex($.base64.btoa(doSignatureInfo.command));
var version_hex = base64ToHex($.base64.btoa(doSignatureInfo.version));
var uploadFlag_hex = base64ToHex($.base64.btoa(doSignatureInfo.uploadFlag));
var hostReferenceNumber_hex = base64ToHex($.base64.btoa(doSignatureInfo.hostReferenceNumber));
var edcType_hex = base64ToHex($.base64.btoa(doSignatureInfo.edcType));
var timeout_hex = base64ToHex($.base64.btoa(doSignatureInfo.timeout));
var elements = [this.mStx.code];
elements.push(command_hex);
elements.push(this.mFS.code);
elements.push(version_hex);
elements.push(this.mFS.code);
if(uploadFlag_hex != ''){
elements.push(uploadFlag_hex);
}
elements.push(this.mFS.code);
if(hostReferenceNumber_hex != ''){
elements.push(hostReferenceNumber_hex);
}
elements.push(this.mFS.code);
if(edcType_hex != ''){
elements.push(edcType_hex);
}
elements.push(this.mFS.code);
if(timeout_hex != ''){
elements.push(timeout_hex);
}
elements.push(this.mEtx.code);
elements.push(base64ToHex($.base64.btoa(lrc)));
var final_string = elements.join(" ");
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('DoSignature',url,function(response){
callback(response);
},PAX.timeout.DoSignature,fail);
},
PushParams : function(params,type,objectInfo){
var empty = 0;
var arr = [];
arr = arr.concat(params);
for(name in objectInfo){
if(objectInfo[name] == '' && type!="additionalInformation")
{
arr.push(this.mUS.hex);
continue;
}
if(type == "additionalInformation"){
if(objectInfo[name] == ''){
continue;
}
empty++;
arr.push(name+"="+objectInfo[name].toString());
}else{
empty++;
arr.push(objectInfo[name].toString());
}
arr.push(this.mUS.hex);
}
arr.pop();
if(empty==0 && type!="additionalInformation"){
arr = params;
}
if(empty==0 && type=="additionalInformation"){
arr.push(this.mFS.hex);
}
//console.log(params);
return arr;
},
AddBase64 : function(elements,type,objectInfo){
//console.log(objectInfo);
var empty = 0;
var arr = [];
arr = arr.concat(elements);
for(name in objectInfo){
if(objectInfo[name] == '' && type!="additionalInformation")
{
arr.push(this.mUS.code);
continue;
}
if(type == "additionalInformation"){
if(objectInfo[name] == '')
continue;
empty++;
arr.push(base64ToHex($.base64.btoa(name+"="+objectInfo[name].toString())));
}else{
empty++;
arr.push(base64ToHex($.base64.btoa(objectInfo[name].toString())));
}
arr.push(this.mUS.code);
}
arr.pop();
if(empty==0 && type!="additionalInformation"){
arr = elements;
}
if(empty==0 && type=="additionalInformation"){
arr.push(this.mFS.code);
}
//console.log(arr);
return arr;
},
//DO Credit
DoCredit : function(doCreditInfo,callback,fail){
var amountInformation,accountInformation,traceInformation,avsInformation,cashierInformation,commercialInformation,motoEcommerce,additionalInformation;
var params = [this.mStx.hex,doCreditInfo.command, this.mFS.hex, doCreditInfo.version];
params.push(this.mFS.hex);
if(doCreditInfo.transactionType != ''){
params.push(doCreditInfo.transactionType);
}
params.push(this.mFS.hex);
params = this.PushParams(params,"amountInformation",doCreditInfo.amountInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"accountInformation",doCreditInfo.accountInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"traceInformation",doCreditInfo.traceInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"avsInformation",doCreditInfo.avsInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"cashierInformation",doCreditInfo.cashierInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"commercialInformation",doCreditInfo.commercialInformation);
params.push(this.mFS.hex);
params = this.PushParams(params,"motoEcommerce",doCreditInfo.motoEcommerce);
params.push(this.mFS.hex);
params = this.PushParams(params,"additionalInformation",doCreditInfo.additionalInformation);
params.push(this.mEtx.hex);
var lrc = this.getLRC(params);
console.log(params);
//prepare for base64 encoding.
var command_hex = base64ToHex($.base64.btoa(doCreditInfo.command));
var version_hex = base64ToHex($.base64.btoa(doCreditInfo.version));
var transactionType_hex = base64ToHex($.base64.btoa(doCreditInfo.transactionType));
var amountInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.amountInformation));
var accountInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.accountInformation));
var traceInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.traceInformation));
var avsInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.avsInformation));
var cashierInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.cashierInformation));
var commercialInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.commercialInformation));
var motoEcommerce_hex = base64ToHex($.base64.btoa(doCreditInfo.motoEcommerce));
var additionalInformation_hex = base64ToHex($.base64.btoa(doCreditInfo.additionalInformation));
//var elements = [this.mStx.code, command_hex, this.mFS.code, version_hex, this.mFS.code, uploadFlag_hex, this.mFS.code, timeout, this.mEtx.code, base64ToHex($.base64.btoa(lrc))];
var elements = [this.mStx.code];
elements.push(command_hex);
elements.push(this.mFS.code);
elements.push(version_hex);
elements.push(this.mFS.code);
if(transactionType_hex != ''){
elements.push(transactionType_hex);
}
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"amountInformation",doCreditInfo.amountInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"accountInformation",doCreditInfo.accountInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"traceInformation",doCreditInfo.traceInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"avsInformation",doCreditInfo.avsInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"cashierInformation",doCreditInfo.cashierInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"commercialInformation",doCreditInfo.commercialInformation);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"motoEcommerce",doCreditInfo.motoEcommerce);
elements.push(this.mFS.code);
elements = this.AddBase64(elements,"additionalInformation",doCreditInfo.additionalInformation);
elements.push(this.mEtx.code);
elements.push(base64ToHex($.base64.btoa(lrc)));
console.log("elements");
console.log(elements);
var final_string = elements.join(" ");
var final_b64 = hexToBase64(final_string);
console.log("LRC: " + lrc);
console.log("Base64: " + final_b64);
// if(customData != ''){
// final_b64 = hexToBase64(final_string+"&custom_data=<PAX>"+customData+"</PAX>");
// }
var url = this.mDestinationIP + '?' + final_b64;
console.log("URL: " + url);
this.HttpCommunication('DoCredit',url,function(response){
callback(response);
},PAX.timeout.DoCredit,fail);
}
};
return PAX;
});

View File

@@ -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('<div class="footer"><div class="button cancel">Ok</div></div>');
}
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);
},
});
});

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" inherit_id="point_of_sale.template">
<t t-name="PAXPaymentTransactionPopupWidget">
<div class="modal-dialog">
<div class="popup">
<p class="title">PAX Electronic Payment</p>
<p class="body"></p>
</div>
</div>
</t>
<t t-extend="PaymentScreen-Paymentlines">
<t t-jquery=".col-name" t-operation="inner">
<t t-if="line.cashregister.journal.type === 'bank'">
<t t-if="line.pax_txn_pending">
<div>WAITING FOR TXN</div>
</t>
<t t-if="! line.pax_txn_pending">
<t t-esc='line.name' />
</t>
<t t-else="">
<span class="btn btn-small pax_send_transaction">Send</span>
</t>
</t>
<t t-if="line.cashregister.journal.type !== 'bank'">
<t t-esc='line.name' />
</t>
</t>
<t t-jquery="tbody tr.paymentline.selected">
this.removeAttr('class');
this.attr('t-attf-class', 'paymentline selected #{line.pax_txn_pending ? \'o_pos_pax_txn_pending\' : \'\'}');
</t>
<t t-jquery="tbody tr.paymentline[t-att-data-cid*='line.cid']">
this.removeAttr('class');
this.attr('t-attf-class', 'paymentline #{line.pax_txn_pending ? \'o_pos_pax_txn_pending\' : \'\'}');
</t>
<t t-jquery="tbody tr td.col-tendered.edit">
this.removeAttr('class');
this.attr('t-attf-class', 'col-tendered edit #{line.pax_txn_pending ? \'o_pos_pax_txn_pending\' : \'\'}');
</t>
</t>
<t t-name="PosPAXSignature">
<t t-foreach="paymentlines" t-as="paymentline">
<t t-if="!gift &amp;&amp; paymentline.pax_txn_id &amp;&amp; ! printed_signature">
<br />
<div>CARDHOLDER WILL PAY CARD ISSUER</div>
<div>ABOVE AMOUNT PURSUANT</div>
<div>TO CARDHOLDER AGREEMENT</div>
<br />
<br />
<div>X______________________________</div>
<t t-set="printed_signature" t-value="true"/>
</t>
</t>
</t>
<t t-extend="XmlReceipt">
<t t-jquery="t[t-foreach*='paymentlines'][t-as*='line']" t-operation="append">
<t t-if="!gift &amp;&amp; line.pax_auth_code">
<line line-ratio="1">
<left><pre> APPROVAL CODE:</pre><t t-esc="line.pax_approval"/></left>
</line>
</t>
</t>
<t t-jquery="receipt" t-operation="append">
<div>
<t t-call="PosPAXSignature"/>
</div>
</t>
</t>
<t t-extend="PosTicket">
<t t-jquery="t[t-foreach*='paymentlines'][t-as*='line']" t-operation="append">
<tr>
<td colspan="2">
<t t-if="!gift &amp;&amp; line.pax_auth_code">
&amp;nbsp;&amp;nbsp;APPROVAL CODE: <t t-esc="line.pax_approval"/>
</t>
</td>
</tr>
</t>
<t t-jquery="t[t-if*='receipt.footer']" t-operation="after">
<div class="pos-center-align">
<t t-call="PosPAXSignature"/>
</div>
</t>
</t>
</templates>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="pos_config_view_form_inherit_pos_pax" model="ir.ui.view">
<field name="name">pos.config.form.inherit.pax</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='payment_methods']" position="after">
<div class="col-12 col-lg-6 o_setting_box" id="pos_pax">
<div class="o_setting_right_pane">
<strong class="o_form_label">PAX</strong>
<div>
<p>Your PAX Device Endpoint</p>
<field name="pax_endpoint"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_pax/static/src/js/jquery_base64.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/pax_device.js"></script>
<script type="text/javascript" src="/pos_pax/static/src/js/pos_pax.js"></script>
<link rel="stylesheet" href="/pos_pax/static/src/css/pos_pax.css" />
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<odoo>
<record id="view_account_journal_pos_user_form" model="ir.ui.view">
<field name="name">POS Journal</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="point_of_sale.view_account_journal_pos_user_form"></field>
<field name="arch" type="xml">
<xpath expr="//group[@name='amount_authorized']" position="after">
<group attrs="{'invisible': [('type', '!=', 'bank')]}">
<field name="pos_use_pax"/>
</group>
</xpath>
</field>
</record>
<record id="view_account_bank_journal_form_inherited_pos_pax" model="ir.ui.view">
<field name="name">account.bank.journal.form.inherited.pos.pax</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="point_of_sale.view_account_bank_journal_form_inherited_pos"></field>
<field name="arch" type="xml">
<xpath expr="//field[@name='journal_user']" position="after">
<field name="pos_use_pax" attrs="{'invisible': [('journal_user', '=', False)]}"/>
</xpath>
</field>
</record>
<record id="view_pos_order" model="ir.ui.view">
<field name="name">POS orders</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount']" position="before">
<field name="pax_card_number"/>
<field name="pax_txn_id"/>
</xpath>
</field>
</record>
</odoo>