Skip to content

Commit e1b5ea6

Browse files
author
John75SunCity
committed
feat: Add barcode generation and printing for staging locations
1 parent bbba8ca commit e1b5ea6

File tree

6 files changed

+291
-1
lines changed

6 files changed

+291
-1
lines changed

records_management/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"report/container_barcode_reports.xml",
171171
"report/document_temp_barcode_reports.xml",
172172
"report/file_barcode_reports.xml",
173+
"report/staging_location_barcode_reports.xml",
173174
# Base & Rate Reports
174175
"report/base_rate_reports.xml",
175176
"report/billing_period_reports.xml",

records_management/controllers/portal.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6727,6 +6727,56 @@ def portal_staging_location_delete(self, location_id, **post):
67276727
'error_details': str(e)
67286728
})
67296729

6730+
@http.route(['/my/inventory/location/<int:location_id>/print-barcode'], type='http', auth='user', website=True)
6731+
def portal_staging_location_print_barcode(self, location_id, **kw):
6732+
"""Generate and print barcode label for staging location."""
6733+
location = request.env['customer.staging.location'].sudo().browse(location_id)
6734+
partner = request.env.user.partner_id.commercial_partner_id
6735+
6736+
# Security check
6737+
if not location.exists() or location.partner_id.commercial_partner_id != partner:
6738+
return request.redirect('/my/inventory/locations?error=access_denied')
6739+
6740+
# Generate barcode if not exists
6741+
if not location.barcode:
6742+
location.action_generate_barcode()
6743+
6744+
# Render barcode label as PDF
6745+
report = request.env.ref('records_management.action_report_staging_location_barcode', raise_if_not_found=False)
6746+
if report:
6747+
pdf_content, _ = request.env['ir.actions.report'].sudo()._render_qweb_pdf(report.report_name, [location.id])
6748+
pdfhttpheaders = [
6749+
('Content-Type', 'application/pdf'),
6750+
('Content-Length', len(pdf_content)),
6751+
('Content-Disposition', 'attachment; filename="Location-Barcode-%s.pdf"' % (location.barcode or location.id)),
6752+
]
6753+
return request.make_response(pdf_content, headers=pdfhttpheaders)
6754+
else:
6755+
# Fallback: render simple HTML barcode page
6756+
return request.render('records_management.portal_staging_location_barcode', {
6757+
'location': location,
6758+
'page_name': 'location_barcode',
6759+
})
6760+
6761+
@http.route(['/my/inventory/location/<int:location_id>/generate-barcode'], type='json', auth='user', methods=['POST'])
6762+
def portal_staging_location_generate_barcode(self, location_id, **kw):
6763+
"""AJAX endpoint to generate barcode for staging location."""
6764+
try:
6765+
location = request.env['customer.staging.location'].sudo().browse(location_id)
6766+
partner = request.env.user.partner_id.commercial_partner_id
6767+
6768+
if not location.exists() or location.partner_id.commercial_partner_id != partner:
6769+
return {'success': False, 'error': 'Access denied'}
6770+
6771+
location.action_generate_barcode()
6772+
return {
6773+
'success': True,
6774+
'barcode': location.barcode,
6775+
'message': 'Barcode generated successfully'
6776+
}
6777+
except Exception as e:
6778+
return {'success': False, 'error': str(e)}
6779+
67306780
@http.route(['/my/inventory/location/<int:location_id>'], type='http', auth='user', website=True)
67316781
def portal_staging_location_detail(self, location_id, **kw):
67326782
"""View containers at a specific staging location"""

records_management/models/customer_staging_location.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ class CustomerStagingLocation(models.Model):
136136
help="Containers awaiting pickup at this location"
137137
)
138138

139+
# ============================================================================
140+
# BARCODE - For location labels customers can print
141+
# ============================================================================
142+
barcode = fields.Char(
143+
string="Location Barcode",
144+
copy=False,
145+
index=True,
146+
help="Unique barcode for this staging location. "
147+
"Customers can print labels with this barcode to identify their pickup locations."
148+
)
149+
139150
# ============================================================================
140151
# LOCATION DETAILS
141152
# ============================================================================
@@ -290,6 +301,33 @@ def action_view_containers(self):
290301
'context': {'default_customer_staging_location_id': self.id}
291302
}
292303

304+
def action_generate_barcode(self):
305+
"""Generate a unique barcode for this staging location."""
306+
self.ensure_one()
307+
if not self.barcode:
308+
# Use sequence or generate based on ID
309+
sequence = self.env['ir.sequence'].sudo().search([
310+
('code', '=', 'customer.staging.location.barcode')
311+
], limit=1)
312+
if sequence:
313+
self.barcode = sequence.next_by_id()
314+
else:
315+
# Fallback: generate from partner code + location type + ID
316+
partner_code = (self.partner_id.ref or 'CUST')[:4].upper()
317+
loc_type = (self.location_type or 'LOC')[:3].upper()
318+
self.barcode = 'SL-%s-%s-%06d' % (partner_code, loc_type, self.id)
319+
return True
320+
321+
def action_print_barcode(self):
322+
"""Print barcode label for this staging location."""
323+
self.ensure_one()
324+
# Generate barcode if not exists
325+
if not self.barcode:
326+
self.action_generate_barcode()
327+
328+
# Return report action for printing
329+
return self.env.ref('records_management.action_report_staging_location_barcode').report_action(self)
330+
293331
def name_get(self):
294332
"""Display complete path"""
295333
return [(location.id, location.complete_name) for location in self]
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<data noupdate="0">
4+
<!-- Staging Location Barcode Report Action -->
5+
<record id="action_report_staging_location_barcode" model="ir.actions.report">
6+
<field name="name">Staging Location Barcode Labels</field>
7+
<field name="model">customer.staging.location</field>
8+
<field name="report_type">qweb-pdf</field>
9+
<field name="report_name">records_management.report_staging_location_barcode</field>
10+
<field name="report_file">records_management.report_staging_location_barcode</field>
11+
<field name="print_report_name">'Location_Barcode_%s' % (object.barcode or object.id)</field>
12+
<field name="binding_model_id" ref="records_management.model_customer_staging_location"/>
13+
<field name="binding_type">report</field>
14+
<field name="paperformat_id" ref="container_barcode_reports.paperformat_label_sheet"/>
15+
<field name="attachment_use" eval="False"/>
16+
<field name="attachment" eval="False"/>
17+
</record>
18+
</data>
19+
20+
<data>
21+
<!-- Staging Location Barcode Labels Template -->
22+
<template id="report_staging_location_barcode">
23+
<t t-call="web.basic_layout">
24+
<t t-call="web.html_container">
25+
<!-- 2 columns x 5 rows = 10 labels per page (4" x 2" each for larger location labels) -->
26+
<t t-set="nRows" t-value="5"/>
27+
<t t-set="nCols" t-value="2"/>
28+
<t t-set="labelsPerPage" t-value="nRows * nCols"/>
29+
30+
<!-- Loop through pages -->
31+
<t t-foreach="[docs[x:x + labelsPerPage] for x in range(0, len(docs), labelsPerPage)]" t-as="page_docs">
32+
<div class="page" style="padding: 5mm; font-family: Arial, sans-serif;">
33+
<table class="table table-borderless" style="width:100%; table-layout:fixed;">
34+
<t t-foreach="range(nRows)" t-as="row">
35+
<tr>
36+
<t t-foreach="range(nCols)" t-as="col">
37+
<t t-set="idx" t-value="row * nCols + col"/>
38+
<td style="width:50%; height:50mm; vertical-align:middle; padding:2mm;">
39+
<t t-if="idx &lt; len(page_docs)">
40+
<t t-set="loc" t-value="page_docs[idx]"/>
41+
<div style="border:2px solid #333; padding:3mm; height:46mm; text-align:center; border-radius:3mm;">
42+
<!-- Location Type Badge -->
43+
<div style="margin-bottom:1mm;">
44+
<span style="background:#0066cc; color:white; padding:1mm 3mm; border-radius:2mm; font-size:8pt; text-transform:uppercase;">
45+
<t t-esc="dict(loc._fields['location_type'].selection).get(loc.location_type, 'LOCATION')"/>
46+
</span>
47+
</div>
48+
49+
<!-- Location Name - LARGE -->
50+
<div style="font-weight:bold; font-size:14pt; margin-bottom:2mm; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
51+
<t t-esc="loc.name"/>
52+
</div>
53+
54+
<!-- Full Path - Smaller -->
55+
<div style="font-size:8pt; color:#555; margin-bottom:2mm; max-height:8mm; overflow:hidden;">
56+
<t t-esc="loc.complete_name"/>
57+
</div>
58+
59+
<!-- Barcode -->
60+
<t t-if="loc.barcode">
61+
<img t-att-src="'/report/barcode/Code128/%s?width=350&amp;height=50' % loc.barcode"
62+
style="width:95%; height:14mm;"/>
63+
<div style="font-size:12pt; font-weight:bold; margin-top:1mm; letter-spacing:1px;">
64+
<t t-esc="loc.barcode"/>
65+
</div>
66+
</t>
67+
<t t-else="">
68+
<div style="font-size:10pt; color:#999; margin-top:5mm;">
69+
<em>No barcode assigned</em>
70+
</div>
71+
</t>
72+
73+
<!-- Customer Name - Small Footer -->
74+
<t t-if="loc.partner_id">
75+
<div style="font-size:7pt; color:#666; margin-top:1mm; border-top:1px dotted #ccc; padding-top:1mm;">
76+
<t t-esc="loc.partner_id.name"/>
77+
</div>
78+
</t>
79+
</div>
80+
</t>
81+
</td>
82+
</t>
83+
</tr>
84+
</t>
85+
</table>
86+
</div>
87+
</t>
88+
</t>
89+
</t>
90+
</template>
91+
92+
<!-- Fallback HTML Template for Portal (when report not available) -->
93+
<template id="portal_staging_location_barcode" name="Staging Location Barcode Print Page">
94+
<t t-call="portal.portal_layout">
95+
<div class="container mt-4">
96+
<div class="row justify-content-center">
97+
<div class="col-md-8">
98+
<div class="card">
99+
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
100+
<h4 class="mb-0"><i class="fa fa-barcode"></i> Location Barcode Label</h4>
101+
<button class="btn btn-light btn-sm" onclick="window.print();">
102+
<i class="fa fa-print"></i> Print
103+
</button>
104+
</div>
105+
<div class="card-body text-center" style="padding: 30px;">
106+
<!-- Printable Label Area -->
107+
<div class="barcode-label" style="border: 2px solid #333; padding: 20px; display: inline-block; min-width: 300px; border-radius: 8px;">
108+
<!-- Location Type -->
109+
<div style="margin-bottom: 10px;">
110+
<span class="badge badge-primary" style="font-size: 10pt;">
111+
<t t-esc="dict(location._fields['location_type'].selection).get(location.location_type, 'LOCATION')"/>
112+
</span>
113+
</div>
114+
115+
<!-- Location Name -->
116+
<h3 style="font-weight: bold; margin-bottom: 5px;">
117+
<t t-esc="location.name"/>
118+
</h3>
119+
120+
<!-- Full Path -->
121+
<p class="text-muted" style="font-size: 10pt; margin-bottom: 15px;">
122+
<t t-esc="location.complete_name"/>
123+
</p>
124+
125+
<!-- Barcode Image -->
126+
<t t-if="location.barcode">
127+
<div style="margin: 15px 0;">
128+
<img t-att-src="'/report/barcode/Code128/%s?width=400&amp;height=60' % location.barcode"
129+
style="max-width: 100%;"/>
130+
</div>
131+
<div style="font-size: 14pt; font-weight: bold; letter-spacing: 2px;">
132+
<t t-esc="location.barcode"/>
133+
</div>
134+
</t>
135+
<t t-else="">
136+
<div class="alert alert-warning">
137+
<i class="fa fa-exclamation-triangle"></i>
138+
No barcode assigned to this location.
139+
<a t-attf-href="/my/inventory/location/#{location.id}/generate-barcode"
140+
class="btn btn-sm btn-primary ml-2">Generate Barcode</a>
141+
</div>
142+
</t>
143+
144+
<!-- Customer Name -->
145+
<t t-if="location.partner_id">
146+
<div style="margin-top: 15px; padding-top: 10px; border-top: 1px dotted #ccc; font-size: 9pt; color: #666;">
147+
<t t-esc="location.partner_id.name"/>
148+
</div>
149+
</t>
150+
</div>
151+
</div>
152+
<div class="card-footer text-center">
153+
<a href="/my/inventory/locations" class="btn btn-secondary">
154+
<i class="fa fa-arrow-left"></i> Back to Locations
155+
</a>
156+
</div>
157+
</div>
158+
</div>
159+
</div>
160+
</div>
161+
162+
<!-- Print Styles -->
163+
<style type="text/css" media="print">
164+
@page { margin: 0; }
165+
body { margin: 0; }
166+
.container, .card, .card-body {
167+
margin: 0 !important;
168+
padding: 0 !important;
169+
border: none !important;
170+
box-shadow: none !important;
171+
}
172+
.card-header, .card-footer, .btn, nav, header, footer {
173+
display: none !important;
174+
}
175+
.barcode-label {
176+
margin: 20mm auto !important;
177+
page-break-inside: avoid;
178+
}
179+
</style>
180+
</t>
181+
</template>
182+
</data>
183+
</odoo>

records_management/templates/portal_staging_location_forms.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,16 @@
182182
<p class="text-muted">
183183
<t t-esc="dict(location._fields['location_type'].selection).get(location.location_type, location.location_type)"/>
184184
<t t-if="location.department_id"> | <t t-esc="location.department_id.name"/></t>
185+
<t t-if="location.barcode">
186+
| <span class="text-info"><i class="fa fa-barcode"></i> <t t-esc="location.barcode"/></span>
187+
</t>
185188
</p>
186189
</div>
187-
<div>
190+
<div class="btn-group">
191+
<a t-attf-href="/my/inventory/location/#{location.id}/print-barcode"
192+
class="btn btn-info" target="_blank">
193+
<i class="fa fa-print"></i> Print Barcode
194+
</a>
188195
<a t-attf-href="/my/inventory/location/#{location.id}/edit" class="btn btn-primary">
189196
<i class="fa fa-edit"></i> Edit Location
190197
</a>

records_management/templates/portal_staging_locations.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@
132132
<i class="fa fa-clock-o"></i> <t t-esc="location.pending_pickup_count"/> pending pickup
133133
</span>
134134
</t>
135+
<t t-if="location.barcode">
136+
| <span class="text-info">
137+
<i class="fa fa-barcode"></i> <t t-esc="location.barcode"/>
138+
</span>
139+
</t>
135140
</small>
136141
</div>
137142
<div class="btn-group">
@@ -145,6 +150,12 @@
145150
title="Edit Location">
146151
<i class="fa fa-edit"></i> Edit
147152
</a>
153+
<a t-attf-href="/my/inventory/location/#{location.id}/print-barcode"
154+
class="btn btn-sm btn-outline-info"
155+
target="_blank"
156+
title="Print Barcode Label">
157+
<i class="fa fa-print"></i> Print
158+
</a>
148159
</div>
149160
</div>
150161

0 commit comments

Comments
 (0)