Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
aa.gusti
/
odoo-dev
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit 3828f9e8
authored
Dec 04, 2021
by
aa.gusti
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
penambahan autonumber
1 parent
a2786de9
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
552 additions
and
56 deletions
bphtb_kab/__manifest__.py
bphtb_kab/models/__init__.py
bphtb_kab/models/partner.py
bphtb_kab/models/sales.py
bphtb_kab/models/sequence_mixin.py
bphtb_kab/views/partner.xml
bphtb_kab/views/sales.xml
bphtb_kab/__manifest__.py
View file @
3828f9e
...
...
@@ -13,7 +13,7 @@ Memudahkan dalam mengelola tagihan kepada wajib pajak atau wajib retribusi
'category'
:
'Indonesia Goverment'
,
'website'
:
'https://opensipkd.com'
,
'images'
:
[],
'depends'
:
[
'id_gov'
],
'depends'
:
[
'id_gov'
,
'portal'
],
'data'
:
[
'security/account_security.xml'
,
'security/ir.model.access.csv'
,
...
...
bphtb_kab/models/__init__.py
View file @
3828f9e
from
.
import
bphtb_ref
from
.
import
partner
from
.
import
sales
\ No newline at end of file
from
.
import
sequence_mixin
from
.
import
sales
bphtb_kab/models/partner.py
View file @
3828f9e
import
logging
from
psycopg2
import
DatabaseError
,
sql
from
odoo
import
fields
,
models
,
api
,
_
from
odoo.exceptions
import
UserError
# from odoo.tools import sql
_logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -23,15 +26,28 @@ class ResPartner(models.Model):
def
write
(
self
,
vals
):
_logger
.
info
(
vals
)
_logger
.
info
(
self
.
create_uid
)
_logger
.
info
(
self
.
env
)
_logger
.
info
(
self
.
env
.
user
.
id
)
if
self
.
create_uid
.
id
!=
self
.
env
.
user
.
id
:
return
{
'warning'
:
{
'title'
:
_
(
'Warning'
),
'message'
:
_
(
'Modified Record Forbidden'
)
}
}
res
=
super
()
.
write
(
vals
)
if
self
.
env
.
user
.
has_group
(
'bphtb_kab.group_bphtb_ppat'
):
_logger
.
info
(
f
'has groups {self.create_uid.id} {self.env.user.id}'
)
if
self
.
create_uid
.
id
!=
self
.
env
.
user
.
id
:
_logger
.
info
(
'different'
)
raise
UserError
(
_
(
"You are not a record owner"
))
res
=
super
(
ResPartner
,
self
)
.
write
(
vals
)
return
res
def
_increase_rank
(
self
,
field
,
n
=
1
):
if
self
.
ids
and
field
in
[
'customer_rank'
,
'supplier_rank'
]:
try
:
with
self
.
env
.
cr
.
savepoint
(
flush
=
False
):
query
=
sql
.
SQL
(
"""
SELECT {field} FROM res_partner WHERE ID IN
%(partner_ids)
s FOR UPDATE NOWAIT;
UPDATE res_partner SET {field} = {field} +
%(n)
s
WHERE id IN
%(partner_ids)
s
"""
)
.
format
(
field
=
sql
.
Identifier
(
field
))
self
.
env
.
cr
.
execute
(
query
,
{
'partner_ids'
:
tuple
(
self
.
ids
),
'n'
:
n
})
for
partner
in
self
:
self
.
env
.
cache
.
remove
(
partner
,
partner
.
_fields
[
field
])
except
DatabaseError
as
e
:
if
e
.
pgcode
==
'55P03'
:
_logger
.
debug
(
'Another transaction already locked partner rows. Cannot update partner ranks.'
)
else
:
raise
e
bphtb_kab/models/sales.py
View file @
3828f9e
import
re
from
collections
import
defaultdict
from
sqlite3
import
DatabaseError
from
odoo
import
api
,
fields
,
models
,
_
from
odoo.exceptions
import
AccessError
,
UserError
,
ValidationError
from
odoo.tools
import
format_date
from
psycopg2
import
sql
,
DatabaseError
import
logging
...
...
@@ -11,14 +15,17 @@ class BphtbSales(models.Model):
_name
=
'bphtb.sales'
_description
=
'Transaksi BPHTB'
# code = fields.Char(index=True, string='Code')
# name = fields.Char(index=True, string='Name', size=64)
_inherit
=
[
'portal.mixin'
,
'mail.thread'
,
'mail.activity.mixin'
,
'sequence.mixin'
]
name
=
fields
.
Char
(
string
=
'Number'
,
copy
=
False
,
compute
=
'_compute_name'
,
readonly
=
False
,
store
=
True
,
index
=
True
,
tracking
=
True
)
ref
=
fields
.
Char
(
string
=
'Reference'
,
copy
=
False
,
tracking
=
True
)
#
# rate = fields.Float(required=True)
# disc = fields.Float(required=True)
# min_omzet = fields.Integer(required=True)
request_
date
=
fields
.
Date
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
code
=
fields
.
Char
()
date
=
fields
.
Date
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
ppat_id
=
fields
.
Many2one
(
'res.partner'
,
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
...
...
@@ -104,23 +111,23 @@ class BphtbSales(models.Model):
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
njop
=
fields
.
Float
(
compute
=
"_compute_njop"
,
store
=
True
)
jenis_id
=
fields
.
Many2one
(
'bphtb.jenis'
,
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},)
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
rate
=
fields
.
Float
(
related
=
'jenis_id.rate'
,
store
=
True
,
readonly
=
True
)
min_omzet
=
fields
.
Float
(
related
=
'jenis_id.min_omzet'
,
store
=
True
,
readonly
=
True
)
disc_sk
=
fields
.
Char
(
required
=
False
,
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
disc
=
fields
.
Float
(
related
=
'jenis_id.disc'
,
store
=
True
,
readonly
=
True
)
npop
=
fields
.
Float
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
npop
=
fields
.
Float
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
basic_calc
=
fields
.
Float
(
compute
=
"_compute_basic_calc"
)
npopkp
=
fields
.
Float
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
npopkp
=
fields
.
Float
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
basic
=
fields
.
Float
()
fine
=
fields
.
Float
()
amount
=
fields
.
Float
()
disc_amount
=
fields
.
Float
()
payment
=
fields
.
Float
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
payment
=
fields
.
Float
(
readonly
=
True
,
states
=
{
'draft'
:
[(
'readonly'
,
False
)]},
)
owed
=
fields
.
Float
(
compute
=
"_compute_owed"
)
typ
=
fields
.
Selection
([
(
'sspd'
,
'SSPD'
),
...
...
@@ -147,6 +154,7 @@ class BphtbSales(models.Model):
company_id
=
fields
.
Many2one
(
'res.company'
,
default
=
lambda
self
:
self
.
env
.
company
.
id
if
not
self
.
company_id
else
False
)
district_id
=
fields
.
Many2one
(
'res.district'
,
related
=
'company_id.district_id'
,
store
=
True
)
def
_my_check_method
(
self
,
cr
,
uid
,
ids
,
context
=
None
):
# Your code goes here
...
...
@@ -254,16 +262,154 @@ class BphtbSales(models.Model):
rec
.
njop
=
rec
.
njop_bng
+
rec
.
njop_bumi
+
rec
.
njop_bumi_bersama
+
\
rec
.
njop_bng_bersama
@api.depends
(
'name'
,
'state'
)
def
name_get
(
self
):
_logger
.
info
(
"Name Get"
)
result
=
[]
for
move
in
self
:
if
self
.
_context
.
get
(
'name_groupby'
):
name
=
'**
%
s**,
%
s'
%
(
format_date
(
self
.
env
,
move
.
date
),
move
.
_get_sales_display_name
())
if
move
.
ref
:
name
+=
' (
%
s)'
%
move
.
ref
if
move
.
ppat_id
.
name
:
name
+=
' -
%
s'
%
move
.
ppat_id
.
name
else
:
name
=
move
.
_get_sales_display_name
(
show_ref
=
True
)
result
.
append
((
move
.
id
,
name
))
return
result
@api.depends
(
'state'
,
'date'
)
def
_compute_name
(
self
):
_logger
.
info
(
'Compute Name'
)
def
journal_key
(
move
):
return
(
move
.
id
,
move
.
typ
)
def
date_key
(
move
):
return
(
move
.
date
.
year
,
move
.
date
.
month
)
grouped
=
defaultdict
(
# key: journal_id, move_type
lambda
:
defaultdict
(
# key: first adjacent (date.year, date.month)
lambda
:
{
'records'
:
self
.
env
[
'bphtb.sales'
],
'format'
:
False
,
'format_values'
:
False
,
'reset'
:
False
}
)
)
self
=
self
.
sorted
(
lambda
m
:
(
m
.
typ
,
m
.
company_id
,
m
.
date
,
m
.
ref
or
''
,
m
.
id
,))
highest_name
=
self
[
0
]
.
_get_last_sequence
()
if
self
else
False
# Group the moves by journal and month
for
move
in
self
:
if
not
highest_name
and
move
==
self
[
0
]
and
move
.
date
:
# not move.posted_before and
# In the form view, we need to compute a default sequence so that the user can edit
# it. We only check the first move as an approximation (enough for new in form view)
pass
elif
(
move
.
name
and
move
.
name
!=
'/'
)
or
move
.
state
!=
'confirmed'
:
try
:
# if not move.posted_before:
move
.
_constrains_date_sequence
()
# Has already a name or is not posted, we don't add to a batch
continue
except
ValidationError
:
# Has never been posted and the name doesn't match the date: recompute it
pass
group
=
grouped
[
journal_key
(
move
)][
date_key
(
move
)]
#
_logger
.
info
(
group
)
_logger
.
info
(
group
[
'records'
])
if
not
group
[
'records'
]:
# Compute all the values needed to sequence this whole group
move
.
_set_next_sequence
()
group
[
'format'
],
group
[
'format_values'
]
=
move
.
_get_sequence_format_param
(
move
.
name
)
group
[
'reset'
]
=
move
.
_deduce_sequence_number_reset
(
move
.
name
)
group
[
'records'
]
+=
move
_logger
.
info
(
group
)
# Fusion the groups depending on the sequence reset and the format used because `seq` is
# the same counter for multiple groups that might be spread in multiple months.
final_batches
=
[]
for
journal_group
in
grouped
.
values
():
journal_group_changed
=
True
_logger
.
info
(
f
"Jurnal Group {journal_group}"
)
for
date_group
in
journal_group
.
values
():
_logger
.
info
(
f
"Date Group {date_group}"
)
if
(
journal_group_changed
or
final_batches
[
-
1
][
'format'
]
!=
date_group
[
'format'
]
or
dict
(
final_batches
[
-
1
][
'format_values'
],
seq
=
0
)
!=
dict
(
date_group
[
'format_values'
],
seq
=
0
)
):
final_batches
+=
[
date_group
]
journal_group_changed
=
False
elif
date_group
[
'reset'
]
==
'never'
:
final_batches
[
-
1
][
'records'
]
+=
date_group
[
'records'
]
elif
(
date_group
[
'reset'
]
==
'year'
and
final_batches
[
-
1
][
'records'
][
0
]
.
date
.
year
==
date_group
[
'records'
][
0
]
.
date
.
year
):
final_batches
[
-
1
][
'records'
]
+=
date_group
[
'records'
]
else
:
final_batches
+=
[
date_group
]
# Give the name based on previously computed values
for
batch
in
final_batches
:
for
move
in
batch
[
'records'
]:
move
.
name
=
batch
[
'format'
]
.
format
(
**
batch
[
'format_values'
])
batch
[
'format_values'
][
'seq'
]
+=
1
batch
[
'records'
]
.
_compute_split_sequence
()
_logger
.
info
(
batch
[
'records'
])
self
.
filtered
(
lambda
m
:
not
m
.
name
)
.
name
=
'/'
# @api.depends('njop')
# def njop_change(self):
def
action_confirm
(
self
):
self
.
state
=
'confirmed'
_logger
.
info
(
"Confirm"
)
to_post
=
self
if
not
self
.
env
.
su
and
not
self
.
env
.
user
.
has_group
(
'bphtb_kab.group_bphtb_ppat'
):
raise
AccessError
(
_
(
"You don't have the access rights to confirm an sspd."
))
for
sales
in
to_post
:
if
sales
.
state
==
'confirmed'
:
raise
UserError
(
_
(
'The entry
%
s (id
%
s) is already confirmed.'
)
%
(
sales
.
name
,
sales
.
id
))
if
not
sales
.
wp_id
or
not
sales
.
seller_id
:
raise
UserError
(
_
(
"The field 'Wajib Pajak' and 'Penjual' is required."
))
if
not
sales
.
date
or
sales
.
date
>
fields
.
Date
.
context_today
(
self
):
raise
UserError
(
_
(
"The request date is required to validate this document."
))
if
not
sales
.
date
or
sales
.
date
<
fields
.
Date
.
context_today
(
self
):
raise
UserError
(
_
(
"Check request date."
))
# to_post.mapped('line_ids').create_analytic_lines()
to_post
.
write
({
'state'
:
'confirmed'
,
# 'posted_before': True,
})
for
sales
in
to_post
:
sales
.
message_subscribe
([
p
.
id
for
p
in
[
sales
.
ppat_id
]
if
p
not
in
sales
.
sudo
()
.
message_partner_ids
])
customer_count
,
supplier_count
=
defaultdict
(
int
),
defaultdict
(
int
)
for
sales
in
to_post
:
customer_count
[
sales
.
ppat_id
]
+=
1
for
partner
,
count
in
customer_count
.
items
():
(
partner
|
partner
.
commercial_partner_id
)
.
_increase_rank
(
'customer_rank'
,
count
)
# Trigger action for paid invoices in amount is zero
# to_post.filtered(
# lambda m: m.is_invoice(include_receipts=True) and m.currency_id.is_zero(m.amount_total)
# ).action_invoice_paid()
# Force balance check since nothing prevents another module to create an incorrect entry.
# This is performed at the very end to avoid flushing fields before the whole processing.
return
to_post
def
action_cancel
(
self
):
self
.
state
=
'canceled'
def
action_draft
(
self
):
self
.
state
=
'draft'
self
.
name
=
'/'
def
action_print
(
self
):
if
not
self
.
printed
:
...
...
@@ -275,12 +421,6 @@ class BphtbSales(models.Model):
# docids = self.env['sale.order'].search([]).ids
return
self
.
env
.
ref
(
'bphtb_kab.action_report_bphtb_sspd'
)
.
report_action
(
self
)
# None, data=data
def
name_get
(
self
):
result
=
[]
for
record
in
self
:
result
.
append
((
record
.
id
,
"{} ({})"
.
format
(
record
.
wp_name
,
record
.
nop
)))
return
result
def
_validate_wp_seller
(
self
):
if
self
.
wp_id
==
self
.
seller_id
:
raise
ValueError
(
"Pembeli dan penjual harus berbeda"
)
...
...
@@ -297,3 +437,81 @@ class BphtbSales(models.Model):
# # Your code goes here
# self._validate_wp_seller()
# return res
#
def
_get_sales_display_name
(
self
,
show_ref
=
False
):
''' Helper to get the display name of an invoice depending of its type.
:param show_ref: A flag indicating of the display name must include or not the journal entry reference.
:return: A string representing the invoice.
'''
self
.
ensure_one
()
draft_name
=
''
if
self
.
state
==
'draft'
:
# draft_name += {
# 'out_invoice': _('Draft SSPD'),
# 'out_refund': _('Draft Credit Note'),
# 'in_invoice': _('Draft Bill'),
# 'in_refund': _('Draft Vendor Credit Note'),
# 'out_receipt': _('Draft Sales Receipt'),
# 'in_receipt': _('Draft Purchase Receipt'),
# 'entry': _('Draft Entry'),
# }[self.move_type]
if
not
self
.
name
or
self
.
name
==
'/'
:
draft_name
+=
' (*
%
s)'
%
str
(
self
.
id
)
else
:
draft_name
+=
' '
+
self
.
name
return
draft_name
or
self
.
name
# return draft_name or self.name + (show_ref and self.ref
# and ' (%s%s)' % (self.ref[:50],
# '...' if len(self.ref) > 50 else '') or '')
def
_get_report_base_filename
(
self
):
return
self
.
_get_sales_display_name
()
def
_get_last_sequence_domain
(
self
,
relaxed
=
False
):
self
.
ensure_one
()
if
not
self
.
date
:
# or not self.journal_id
return
"WHERE FALSE"
,
{}
where_string
=
"WHERE company_id=
%(company_id)
s AND typ =
%(typ)
s AND name != '/'"
param
=
{
'typ'
:
self
.
typ
,
'company_id'
:
self
.
company_id
.
id
}
if
not
relaxed
:
domain
=
[(
'typ'
,
'='
,
self
.
typ
),
(
'id'
,
'!='
,
self
.
id
or
self
.
_origin
.
id
),
(
'name'
,
'not in'
,
(
'/'
,
''
,
False
))]
#
reference_move_name
=
self
.
search
(
domain
+
[(
'date'
,
'<='
,
self
.
date
)],
order
=
'date desc'
,
limit
=
1
)
.
name
if
not
reference_move_name
:
reference_move_name
=
self
.
search
(
domain
,
order
=
'date asc'
,
limit
=
1
)
.
name
sequence_number_reset
=
self
.
_deduce_sequence_number_reset
(
reference_move_name
)
if
sequence_number_reset
==
'year'
:
where_string
+=
" AND date_trunc('year', date::timestamp without time zone) = date_trunc('year', "
\
"
%(date)
s) "
param
[
'date'
]
=
self
.
date
param
[
'anti_regex'
]
=
re
.
sub
(
r"\?P<\w+>"
,
"?:"
,
self
.
_sequence_monthly_regex
.
split
(
'(?P<seq>'
)[
0
])
+
'$'
elif
sequence_number_reset
==
'month'
:
where_string
+=
" AND date_trunc('month', date::timestamp without time zone) = date_trunc('month', "
\
"
%(date)
s) "
param
[
'date'
]
=
self
.
date
else
:
param
[
'anti_regex'
]
=
re
.
sub
(
r"\?P<\w+>"
,
"?:"
,
self
.
_sequence_yearly_regex
.
split
(
'(?P<seq>'
)[
0
])
+
'$'
if
param
.
get
(
'anti_regex'
):
# and not self.journal_id.sequence_override_regex:
where_string
+=
" AND sequence_prefix !~
%(anti_regex)
s "
# if self.journal_id.refund_sequence:
# if self.move_type in ('out_refund', 'in_refund'):
# where_string += " AND move_type IN ('out_refund', 'in_refund') "
# else:
# where_string += " AND move_type NOT IN ('out_refund', 'in_refund') "
return
where_string
,
param
def
_get_starting_sequence
(
self
):
self
.
ensure_one
()
# self.typ.upper()
prefix1
=
F
"{self.typ.upper()}/{self.company_id.district_id.state_id.code}{self.company_id.district_id.code}"
starting_sequence
=
"
%
s/
%04
d/
%02
d/00000"
%
(
prefix1
,
self
.
date
.
year
,
self
.
date
.
month
)
# if self.journal_id.refund_sequence and self.move_type in ('out_refund', 'in_refund'):
# starting_sequence = "R" + starting_sequence
return
starting_sequence
bphtb_kab/models/sequence_mixin.py
0 → 100644
View file @
3828f9e
# -*- coding: utf-8 -*-
import
logging
from
odoo
import
api
,
fields
,
models
,
_
from
odoo.exceptions
import
ValidationError
from
odoo.tools.misc
import
format_date
import
re
from
psycopg2
import
sql
_logger
=
logging
.
getLogger
(
__name__
)
class
SequenceMixin
(
models
.
AbstractModel
):
"""Mechanism used to have an editable sequence number.
Be careful of how you use this regarding the prefixes. More info in the
docstring of _get_last_sequence.
"""
_name
=
'sequence.mixin'
_description
=
"Automatic sequence"
_sequence_field
=
"name"
_sequence_date_field
=
"date"
_sequence_index
=
False
_sequence_monthly_regex
=
r'^(?P<prefix1>.*?)(?P<year>((?<=\D)|(?<=^))((19|20|21)\d{2}|(\d{2}(?=\D))))('
\
r'?P<prefix2>\D*?)(?P<month>(0[1-9]|1[0-2]))(?P<prefix3>\D+?)(?P<seq>\d*)('
\
r'?P<suffix>\D*?)$'
_sequence_yearly_regex
=
r'^(?P<prefix1>.*?)(?P<year>((?<=\D)|(?<=^))((19|20|21)?\d{2}))(?P<prefix2>\D+?)('
\
r'?P<seq>\d*)(?P<suffix>\D*?)$'
_sequence_fixed_regex
=
r'^(?P<prefix1>.*?)(?P<seq>\d{0,9})(?P<suffix>\D*?)$'
sequence_prefix
=
fields
.
Char
(
compute
=
'_compute_split_sequence'
,
store
=
True
)
sequence_number
=
fields
.
Integer
(
compute
=
'_compute_split_sequence'
,
store
=
True
)
def
init
(
self
):
# Add an index to optimise the query searching for the highest sequence number
if
not
self
.
_abstract
and
self
.
_sequence_index
:
index_name
=
self
.
_table
+
'_sequence_index'
self
.
env
.
cr
.
execute
(
'SELECT indexname FROM pg_indexes WHERE indexname =
%
s'
,
(
index_name
,))
if
not
self
.
env
.
cr
.
fetchone
():
self
.
env
.
cr
.
execute
(
sql
.
SQL
(
"""
CREATE INDEX {index_name} ON {table} ({sequence_index}, sequence_prefix desc, sequence_number
desc, {field});
CREATE INDEX {index2_name} ON {table} ({sequence_index}, id desc, sequence_prefix);
"""
)
.
format
(
sequence_index
=
sql
.
Identifier
(
self
.
_sequence_index
),
index_name
=
sql
.
Identifier
(
index_name
),
index2_name
=
sql
.
Identifier
(
index_name
+
"2"
),
table
=
sql
.
Identifier
(
self
.
_table
),
field
=
sql
.
Identifier
(
self
.
_sequence_field
),
))
def
__init__
(
self
,
pool
,
cr
):
api
.
constrains
(
self
.
_sequence_field
,
self
.
_sequence_date_field
)(
type
(
self
)
.
_constrains_date_sequence
)
return
super
()
.
__init__
(
pool
,
cr
)
def
_constrains_date_sequence
(
self
):
# Make it possible to bypass the constraint to allow edition of already messed up documents.
# /!\ Do not use this to completely disable the constraint as it will make this mixin unreliable.
constraint_date
=
fields
.
Date
.
to_date
(
self
.
env
[
'ir.config_parameter'
]
.
sudo
()
.
get_param
(
'sequence.mixin.constraint_start_date'
,
'1970-01-01'
))
for
record
in
self
:
date
=
fields
.
Date
.
to_date
(
record
[
record
.
_sequence_date_field
])
sequence
=
record
[
record
.
_sequence_field
]
if
sequence
and
date
and
date
>
constraint_date
:
format_values
=
record
.
_get_sequence_format_param
(
sequence
)[
1
]
if
(
format_values
[
'year'
]
and
format_values
[
'year'
]
!=
date
.
year
%
10
**
len
(
str
(
format_values
[
'year'
]))
or
format_values
[
'month'
]
and
format_values
[
'month'
]
!=
date
.
month
):
raise
ValidationError
(
_
(
"The
%(date_field)
s (
%(date)
s) doesn't match the
%(sequence_field)
s (
%(sequence)
s).
\n
"
"You might want to clear the field
%(sequence_field)
s before proceeding with the change of "
"the date."
,
date
=
format_date
(
self
.
env
,
date
),
sequence
=
sequence
,
date_field
=
record
.
_fields
[
record
.
_sequence_date_field
]
.
_description_string
(
self
.
env
),
sequence_field
=
record
.
_fields
[
record
.
_sequence_field
]
.
_description_string
(
self
.
env
),
))
@api.depends
(
lambda
self
:
[
self
.
_sequence_field
])
def
_compute_split_sequence
(
self
):
for
record
in
self
:
sequence
=
record
[
record
.
_sequence_field
]
or
''
regex
=
re
.
sub
(
r"\?P<\w+>"
,
"?:"
,
record
.
_sequence_fixed_regex
.
replace
(
r"?P<seq>"
,
""
))
# make the seq the only matching group
matching
=
re
.
match
(
regex
,
sequence
)
record
.
sequence_prefix
=
sequence
[:
matching
.
start
(
1
)]
record
.
sequence_number
=
int
(
matching
.
group
(
1
)
or
0
)
@api.model
def
_deduce_sequence_number_reset
(
self
,
name
):
"""Detect if the used sequence resets yearly, montly or never.
:param name: the sequence that is used as a reference to detect the resetting
periodicity. Typically, it is the last before the one you want to give a
sequence.
"""
for
regex
,
ret_val
,
requirements
in
[
(
self
.
_sequence_monthly_regex
,
'month'
,
[
'seq'
,
'month'
,
'year'
]),
(
self
.
_sequence_yearly_regex
,
'year'
,
[
'seq'
,
'year'
]),
(
self
.
_sequence_fixed_regex
,
'never'
,
[
'seq'
]),
]:
_logger
.
info
(
f
"{regex}, {ret_val}, {requirements} "
)
match
=
re
.
match
(
regex
,
name
or
''
)
if
match
:
groupdict
=
match
.
groupdict
()
if
all
(
req
in
groupdict
for
req
in
requirements
):
return
ret_val
raise
ValidationError
(
_
(
'The sequence regex should at least contain the seq grouping keys. For instance:
\n
'
'^(?P<prefix1>.*?)(?P<seq>
\
d*)(?P<suffix>
\
D*?)$'
))
def
_get_last_sequence_domain
(
self
,
relaxed
=
False
):
"""Get the sql domain to retreive the previous sequence number.
This function should be overriden by models heriting from this mixin.
:param relaxed: see _get_last_sequence.
:returns: tuple(where_string, where_params): with
where_string: the entire SQL WHERE clause as a string.
where_params: a dictionary containing the parameters to substitute
at the execution of the query.
"""
self
.
ensure_one
()
return
""
,
{}
def
_get_starting_sequence
(
self
):
"""Get a default sequence number.
This function should be overriden by models heriting from this mixin
This number will be incremented so you probably want to start the sequence at 0.
:return: string to use as the default sequence to increment
"""
self
.
ensure_one
()
return
"00000000"
def
_get_last_sequence
(
self
,
relaxed
=
False
):
"""Retrieve the previous sequence.
This is done by taking the number with the greatest alphabetical value within
the domain of _get_last_sequence_domain. This means that the prefix has a
huge importance.
For instance, if you have INV/2019/0001 and INV/2019/0002, when you rename the
last one to FACT/2019/0001, one might expect the next number to be
FACT/2019/0002 but it will be INV/2019/0002 (again) because INV > FACT.
Therefore, changing the prefix might not be convenient during a period, and
would only work when the numbering makes a new start (domain returns by
_get_last_sequence_domain is [], i.e: a new year).
:param field_name: the field that contains the sequence.
:param relaxed: this should be set to True when a previous request didn't find
something without. This allows to find a pattern from a previous period, and
try to adapt it for the new period.
:return: the string of the previous sequence or None if there wasn't any.
"""
self
.
ensure_one
()
if
self
.
_sequence_field
not
in
self
.
_fields
or
not
self
.
_fields
[
self
.
_sequence_field
]
.
store
:
raise
ValidationError
(
_
(
'
%
s is not a stored field'
,
self
.
_sequence_field
))
where_string
,
param
=
self
.
_get_last_sequence_domain
(
relaxed
)
if
self
.
id
or
self
.
id
.
origin
:
where_string
+=
" AND id !=
%(id)
s "
param
[
'id'
]
=
self
.
id
or
self
.
id
.
origin
query
=
"""
UPDATE {table} SET write_date = write_date WHERE id = (
SELECT id FROM {table}
{where_string}
AND sequence_prefix = (SELECT sequence_prefix FROM {table} {where_string} ORDER BY id DESC LIMIT 1)
ORDER BY sequence_number DESC
LIMIT 1
)
RETURNING {field};
"""
.
format
(
table
=
self
.
_table
,
where_string
=
where_string
,
field
=
self
.
_sequence_field
,
)
self
.
flush
([
self
.
_sequence_field
,
'sequence_number'
,
'sequence_prefix'
])
self
.
env
.
cr
.
execute
(
query
,
param
)
return
(
self
.
env
.
cr
.
fetchone
()
or
[
None
])[
0
]
def
_get_sequence_format_param
(
self
,
previous
):
"""Get the python format and format values for the sequence.
:param previous: the sequence we want to extract the format from
:return tuple(format, format_values):
format is the format string on which we should call .format()
format_values is the dict of values to format the `format` string
``format.format(**format_values)`` should be equal to ``previous``
"""
sequence_number_reset
=
self
.
_deduce_sequence_number_reset
(
previous
)
regex
=
self
.
_sequence_fixed_regex
if
sequence_number_reset
==
'year'
:
regex
=
self
.
_sequence_yearly_regex
elif
sequence_number_reset
==
'month'
:
regex
=
self
.
_sequence_monthly_regex
_logger
.
info
(
f
'{regex} {sequence_number_reset}'
)
format_values
=
re
.
match
(
regex
,
previous
)
.
groupdict
()
format_values
[
'seq_length'
]
=
len
(
format_values
[
'seq'
])
format_values
[
'year_length'
]
=
len
(
format_values
.
get
(
'year'
,
''
))
if
not
format_values
.
get
(
'seq'
)
and
'prefix1'
in
format_values
and
'suffix'
in
format_values
:
# if we don't have a seq, consider we only have a prefix and not a suffix
format_values
[
'prefix1'
]
=
format_values
[
'suffix'
]
format_values
[
'suffix'
]
=
''
for
field
in
(
'seq'
,
'year'
,
'month'
):
format_values
[
field
]
=
int
(
format_values
.
get
(
field
)
or
0
)
placeholders
=
re
.
findall
(
r'(prefix\d|seq|suffix\d?|year|month)'
,
regex
)
format
=
''
.
join
(
"{seq:0{seq_length}d}"
if
s
==
'seq'
else
"{month:02d}"
if
s
==
'month'
else
"{year:0{year_length}d}"
if
s
==
'year'
else
"{
%
s}"
%
s
for
s
in
placeholders
)
return
format
,
format_values
def
_set_next_sequence
(
self
):
"""Set the next sequence.
This method ensures that the field is set both in the ORM and in the database.
This is necessary because we use a database query to get the previous sequence,
and we need that query to always be executed on the latest data.
:param field_name: the field that contains the sequence.
"""
self
.
ensure_one
()
last_sequence
=
self
.
_get_last_sequence
()
new
=
not
last_sequence
if
new
:
last_sequence
=
self
.
_get_last_sequence
(
relaxed
=
True
)
or
self
.
_get_starting_sequence
()
format
,
format_values
=
self
.
_get_sequence_format_param
(
last_sequence
)
if
new
:
format_values
[
'seq'
]
=
0
format_values
[
'year'
]
=
self
[
self
.
_sequence_date_field
]
.
year
%
(
10
**
format_values
[
'year_length'
])
format_values
[
'month'
]
=
self
[
self
.
_sequence_date_field
]
.
month
format_values
[
'seq'
]
=
format_values
[
'seq'
]
+
1
self
[
self
.
_sequence_field
]
=
format
.
format
(
**
format_values
)
self
.
_compute_split_sequence
()
bphtb_kab/views/partner.xml
View file @
3828f9e
<odoo>
<data>
<!-- <record id="res_partner_ppat_search" model="ir.ui.view">-->
<!-- <field name="name">res.partner.ppat.search.inherit</field>-->
<!-- <field name="model">res.partner</field>-->
<!-- <field name="inherit_id" ref="base.view_res_partner_filter"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="//filter[@name='inactive']" position="before">-->
<!-- <filter string="PPAT" name="ppat" domain="[('type','=', 'ppat')]"/>-->
<!-- <filter string="Wajib Pajak" name="wp" domain="[('type','=', 'wp')]"/>-->
<!-- <separator/>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<record
id=
"partner_form_id_bphtb"
model=
"ir.ui.view"
>
<field
name=
"name"
>
partner.form.id.bphtb.inherit
</field>
<field
name=
"model"
>
res.partner
</field>
<field
name=
"inherit_id"
ref=
"id_gov.partner_form_id"
/>
<field
name=
"groups_id"
eval=
"[(6, 0, [ref('bphtb_kab.group_bphtb_ppat') ])]"
/>
<field
name=
"arch"
type=
"xml"
>
<xpath
expr=
"//field[@name='type']"
position=
"attributes"
>
<attribute
name=
"invisible"
>
True
</attribute>
</xpath>
</field>
</record>
<record
id=
"action_ppat_config_bphtb_kab"
model=
"ir.actions.act_window"
>
<field
name=
"name"
>
PPAT
</field>
<field
name=
"res_model"
>
res.partner
</field>
...
...
@@ -35,7 +34,9 @@
<field
name=
"domain"
>
[('type','=','wp')]
</field>
<field
name=
"context"
>
{
'default_is_company': True,
'default_type': 'ppat'}
'default_type': 'wp',
'show_type': 0}
</field>
<field
name=
"help"
type=
"html"
>
<p
class=
"o_view_nocontent_smiling_face"
>
...
...
bphtb_kab/views/sales.xml
View file @
3828f9e
<odoo>
<data>
<record
id=
"action_report_bphtb_sspd"
model=
"ir.actions.report"
>
<field
name=
"name"
>
Format SSPD PDF
</field>
<field
name=
"model"
>
bphtb.sales
</field>
...
...
@@ -10,29 +11,30 @@
<field
name=
"binding_type"
>
report
</field>
</record>
<record
id=
"view_bphtb_sspd_filter"
model=
"ir.ui.view"
>
<record
id=
"view_bphtb_sspd_filter"
model=
"ir.ui.view"
>
<field
name=
"name"
>
view.bphtb.sspd.filter
</field>
<field
name=
"model"
>
bphtb.sales
</field>
<field
name=
"arch"
type=
"xml"
>
<search
string=
"Search SSPD"
>
<!-- <field name="name" string="SSPD" filter_domain="[('nop', 'ilike', self)]"/>-->
<!-- <field name="name" string="SSPD" filter_domain="[('nop', 'ilike', self)]"/>-->
<field
name=
"ppat_id"
/>
<field
name=
"wp_id"
/>
<field
name=
"
request_
date"
/>
<field
name=
"date"
/>
<separator/>
<filter
string=
"Draft"
name=
"draft"
domain=
"[('state', '=', 'draft')]"
help=
"Draft"
/>
<filter
string=
"Confirmed"
name=
"confirmed"
domain=
"[('state', '=', 'confirmed')]"
help=
"Confirmed"
/>
<filter
string=
"Confirmed"
name=
"confirmed"
domain=
"[('state', '=', 'confirmed')]"
help=
"Confirmed"
/>
<filter
string=
"Canceled"
name=
"canceled"
domain=
"[('state', '=', 'canceled')]"
/>
<separator/>
<!-- <filter string="Jenis" name="jenis" date="jenis_id"/>-->
<filter
string=
"Date"
name=
"date"
date=
"
request_
date"
/>
<!-- <separator/>-->
<!-- <filter string="Jenis" name="jenis" date="jenis_id"/>-->
<filter
string=
"Date"
name=
"date"
date=
"date"
/>
<!-- <separator/>-->
<group
expand=
"0"
string=
"Group By"
>
<filter
string=
"PPAT"
name=
"ppat"
domain=
"[]"
context=
"{'group_by': 'ppat_id'}"
/>
<filter
string=
"Jenis"
name=
"jenis"
domain=
"[]"
context=
"{'group_by': 'jenis_id'}"
/>
<filter
string=
"WP"
name=
"wp"
domain=
"[]"
context=
"{'group_by': 'wp_id'}"
/>
<filter
string=
"Status"
name=
"status"
domain=
"[]"
context=
"{'group_by': 'state'}"
/>
<filter
string=
"Date"
name=
"by_date"
domain=
"[]"
context=
"{'group_by': '
request_
date'}"
/>
<filter
string=
"Date"
name=
"by_date"
domain=
"[]"
context=
"{'group_by': 'date'}"
/>
</group>
</search>
</field>
...
...
@@ -54,6 +56,8 @@
<field
name=
"model"
>
bphtb.sales
</field>
<field
name=
"arch"
type=
"xml"
>
<tree
string=
"SSPD"
sample=
"1"
create=
"1"
delete=
"1"
multi_edit=
"0"
>
<field
name=
"date"
/>
<field
name=
"name"
string=
"Invoice No"
/>
<field
name=
"nop"
string=
"NOP"
/>
<field
name=
"tax_year"
string=
"Tahun"
/>
<field
name=
"jenis_id"
string=
"Jenis"
/>
...
...
@@ -94,13 +98,16 @@
attrs=
"{'invisible': [('state', '!=', 'draft')]}"
/>
<widget
name=
"web_ribbon"
title=
"Confirmed"
attrs=
"{'invisible': [('state', '!=', 'confirmed')]}"
/>
<h
1
>
<h
2
>
<field
name=
"company_id"
readonly=
"1"
/>
</h1>
</h2>
<h3>
<field
name=
"district_id"
readonly=
"1"
/>
</h3>
<group>
<field
name=
"typ"
readonly=
"1"
/>
<field
name=
"ppat_id"
domain=
"[('type','=','ppat')]"
/>
<field
name=
"
request_
date"
/>
<field
name=
"date"
/>
</group>
<notebook
col_span=
"4"
>
<page
name=
"op"
string=
"Objek Pajak"
>
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment